@snowcone-app/canvas 0.18.1 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/{EditorCanvasImpl-Bv4yagew.js → EditorCanvasImpl-_3xCnLkx.js} +4 -4
  2. package/dist/{EditorCanvasImpl-Bv4yagew.js.map → EditorCanvasImpl-_3xCnLkx.js.map} +1 -1
  3. package/dist/EditorContext-5QpqAhjh.js.map +1 -1
  4. package/dist/{SnowconeCanvas-C_O-aeaQ.js → SnowconeCanvas-3PA17na3.js} +2 -2
  5. package/dist/{SnowconeCanvas-C_O-aeaQ.js.map → SnowconeCanvas-3PA17na3.js.map} +1 -1
  6. package/dist/api/embed.d.ts +2 -2
  7. package/dist/api/testing.d.ts +3 -0
  8. package/dist/components/editor/EditorBuy.d.ts +16 -5
  9. package/dist/components/editor/RootInternals.d.ts +5 -0
  10. package/dist/components/editor/commerceStore.d.ts +68 -0
  11. package/dist/components/editor/index.d.ts +4 -0
  12. package/dist/components/editor/useProductSelection.d.ts +39 -0
  13. package/dist/design-builder-v4935TeS.js +343 -0
  14. package/dist/design-builder-v4935TeS.js.map +1 -0
  15. package/dist/embed-D2QOpJXG.js +1090 -0
  16. package/dist/embed-D2QOpJXG.js.map +1 -0
  17. package/dist/embed.mjs +27 -25
  18. package/dist/embed.mjs.map +1 -1
  19. package/dist/errors/editorError.d.ts +15 -1
  20. package/dist/index.mjs +40 -39
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/template-fields-Dr3fMItw.js +329 -0
  23. package/dist/template-fields-Dr3fMItw.js.map +1 -0
  24. package/dist/testing/assertRenders.d.ts +53 -0
  25. package/dist/testing/failureFixtures.d.ts +25 -0
  26. package/dist/testing.mjs +191 -80
  27. package/dist/testing.mjs.map +1 -1
  28. package/package.json +3 -3
  29. package/dist/embed-Cdbjay9i.js +0 -961
  30. package/dist/embed-Cdbjay9i.js.map +0 -1
  31. package/dist/template-fields-DgoBtajp.js +0 -668
  32. package/dist/template-fields-DgoBtajp.js.map +0 -1
@@ -34,8 +34,8 @@
34
34
  *
35
35
  * @packageDocumentation
36
36
  */
37
- export { Editor, DesignStateStore } from '../components/editor/index.js';
38
- export type { EditorRootProps, EditorRootBaseProps, EditorCanvasProps, EditorPreviewProps, EditorTextFieldProps, EditorBuyProps, EditorBuyPayload, BuyMissingReason, DesignState, EditorLayersProps, EditorControlsProps, EditorHistoryProps, EditorMenuProps, EditorAddProps, } from '../components/editor/index.js';
37
+ export { Editor, DesignStateStore, useProductSelection } from '../components/editor/index.js';
38
+ export type { EditorRootProps, EditorRootBaseProps, EditorCanvasProps, EditorPreviewProps, EditorTextFieldProps, EditorBuyProps, EditorBuyPayload, BuyMissingReason, DesignState, EditorLayersProps, EditorControlsProps, EditorHistoryProps, EditorMenuProps, EditorAddProps, ProductSelectionApi, ProductOptionChoice, ProductSelectionAttribute, } from '../components/editor/index.js';
39
39
  export { EditorError, isEditorError } from '../errors/editorError.js';
40
40
  export type { EditorErrorCode, EditorErrorData } from '../errors/editorError.js';
41
41
  export { RealtimeEmbed } from '../components/embed-realtime/RealtimeEmbed.js';
@@ -24,3 +24,6 @@
24
24
  export { MockEditorProvider, MockEditorProviderWithState, createMockElement, createMockImageElement, createMockArtboard, waitFor, waitForExport, simulateKeyPress, createMockCanvas, assertElementProperties, getElementBounds, } from '../testing/index.js';
25
25
  export type { MockEditorProviderProps, MockEditorElement, } from '../testing/index.js';
26
26
  export { expectNoEditorErrors, getContainedEditorErrors, resetContainedEditorErrors, } from '../testing/editorErrors.js';
27
+ export { assertRenders, mutateNamedTextLayers } from '../testing/assertRenders.js';
28
+ export type { AssertRendersOptions, AssertRendersResult, RenderableDesign } from '../testing/assertRenders.js';
29
+ export { test403Design, testIncompleteDesign, failingGrant, TEST_403_ASSET_URL, } from '../testing/failureFixtures.js';
@@ -1,14 +1,17 @@
1
1
  import { default as React, CSSProperties, ReactNode } from 'react';
2
- import { CartDetail, RenderProduct } from '@snowcone-app/sdk';
2
+ import { CartDetail, OptionSelection } from '@snowcone-app/sdk';
3
3
  import { ServerRenderRequest } from '../../rendering/serialize-for-server.js';
4
4
  import { DesignState } from './designStateStore.js';
5
5
  /** A structured incompleteness reason — also serialized (comma-joined) into
6
6
  * the button's `data-sc-buy-missing` attribute for agents and styling. */
7
- export type BuyMissingReason = 'variant' | `layer:${string}` | `layer-seed:${string}` | 'empty-design' | 'quantity';
7
+ export type BuyMissingReason = `option:${string}` | 'variant' | `layer:${string}` | `layer-seed:${string}` | 'empty-design' | 'quantity';
8
8
  export interface EditorBuyPayload {
9
9
  productId: string;
10
+ /** The variant (gvid) the selection resolves to. */
10
11
  variantId?: string;
11
12
  quantity: number;
13
+ /** The variant choices at click time, e.g. `{ Color: 'Black', Size: 'M' }`. */
14
+ selection: OptionSelection;
12
15
  /** SDK cart detail (variant resolution + price) when the product carries
13
16
  * catalog `options`; `null` for option-less products. */
14
17
  cartDetail: CartDetail | null;
@@ -22,8 +25,13 @@ export interface EditorBuyProps {
22
25
  /** The final submit — the host's one line. Everything before it (variant,
23
26
  * quantity, completeness, payload assembly) is this part's job. */
24
27
  onAddToCart: (payload: EditorBuyPayload) => void | Promise<void>;
25
- /** Initial quantity (integer ≥ 1; default 1). */
26
- quantity?: number;
28
+ /**
29
+ * Seeds the Buy-owned quantity once on mount (integer ≥ 1; the store
30
+ * defaults to 1). Live reads/writes go through `useProductSelection()` —
31
+ * this is deliberately NOT a controlled prop, so two Buy parts under one
32
+ * Root can never disagree about quantity.
33
+ */
34
+ defaultQuantity?: number;
27
35
  /** Button label. Defaults to "Add to cart". */
28
36
  children?: ReactNode;
29
37
  className?: string;
@@ -31,8 +39,11 @@ export interface EditorBuyProps {
31
39
  }
32
40
  /** Pure completeness evaluation — exported for tests. @internal */
33
41
  export declare function evaluateBuyCompleteness(input: {
34
- product: RenderProduct;
35
42
  quantity: number;
43
+ /** Required attribute keys still unselected (C1 — `option:<key>`). */
44
+ missingOptionKeys: string[];
45
+ /** Full-resolution validity of the selection (C1 — `variant`). */
46
+ selectionValid: boolean;
36
47
  textFields: Array<{
37
48
  name: string;
38
49
  optional: boolean;
@@ -1,5 +1,6 @@
1
1
  import { EditorError } from '../../errors/editorError.js';
2
2
  import { DesignStateStore } from './designStateStore.js';
3
+ import { CommerceStateStore } from './commerceStore.js';
3
4
  /** Imperative ops a mounted `Editor.Canvas` offers sibling parts. Routes
4
5
  * TextField writes through the editor command path (undo/redo, repaint) so
5
6
  * the editor document stays the single write authority while a canvas is
@@ -57,6 +58,10 @@ export declare class EditorPartsRegistry {
57
58
  export interface EditorRootInternals {
58
59
  /** The one live design state under this Root (ADR-0085 §3 / T2). */
59
60
  store: DesignStateStore;
61
+ /** The one commerce-selection state under this Root (ADR-0085 §2 / P2):
62
+ * variant selection + quantity. `Editor.Buy` owns the contract; the host
63
+ * reads/writes it through `useProductSelection()`. */
64
+ commerce: CommerceStateStore;
60
65
  registry: EditorPartsRegistry;
61
66
  /**
62
67
  * Handler/async-time invariant channel (ADR-0085, D11 containment): React
@@ -0,0 +1,68 @@
1
+ import { Combination, OptionAttribute, OptionSelection, RenderProduct } from '@snowcone-app/sdk';
2
+ /**
3
+ * One choice of a variant attribute as the commerce store HANDS IT OUT —
4
+ * always the catalog's choice OBJECT (`toOptionAttributes` normalizes bare
5
+ * strings to `{ label }`). A `hex` marks a color-axis choice (render it as a
6
+ * swatch). NOTE the currency mismatch this type exists to make honest: the
7
+ * SDK's `OptionAttribute` declares `choices: string[]` for its selection
8
+ * math, but the runtime objects flowing through this store are these.
9
+ */
10
+ export interface ProductOptionChoice {
11
+ label?: string;
12
+ hex?: string;
13
+ [key: string]: unknown;
14
+ }
15
+ /** An attribute as `useProductSelection().attributes` returns it: the SDK
16
+ * shape with the choices typed as the objects they actually are. The
17
+ * CURRENCY for `select()` / `selection` / `disabledChoices` is the choice's
18
+ * `label` STRING — pass `choice.label`, never the object. */
19
+ export interface ProductSelectionAttribute extends Omit<OptionAttribute, 'choices'> {
20
+ choices: ProductOptionChoice[];
21
+ }
22
+ /** The label strings of an attribute's choices (legacy attrs may carry bare
23
+ * strings; normalized attrs carry `{ label, hex?, … }` objects). */
24
+ export declare function choiceLabelsOf(attr: OptionAttribute | ProductSelectionAttribute): string[];
25
+ /** Derive the seed selection from the product's initial `variantId` — the
26
+ * combination it resolves to pins one value per variant-affecting attribute.
27
+ * No `variantId` (or no match) seeds an empty selection. */
28
+ export declare function seedSelectionOf(product: RenderProduct, combinations: Combination[]): OptionSelection;
29
+ export declare class CommerceStateStore {
30
+ private product;
31
+ private attrs;
32
+ private combos;
33
+ private selection;
34
+ private quantity;
35
+ private version;
36
+ private readonly listeners;
37
+ constructor(product: RenderProduct);
38
+ getVersion: () => number;
39
+ subscribe: (listener: () => void) => (() => void);
40
+ getAttributes(): Record<string, ProductSelectionAttribute>;
41
+ getSelection(): OptionSelection;
42
+ getQuantity(): number;
43
+ /**
44
+ * Select a choice for a variant attribute. Strict + loud (D4/E3): an
45
+ * attribute key or choice that doesn't exist on the product is a wiring
46
+ * mistake, not user state — throws `E_UNKNOWN_OPTION` listing what IS valid.
47
+ */
48
+ select(key: string, choice: string): void;
49
+ /** Quantity is USER state (completeness rule C5 gates on it) — never throws. */
50
+ setQuantity(quantity: number): void;
51
+ /** Attribute keys still required (non-optional) but unselected — Buy's
52
+ * `option:<key>` reason codes. */
53
+ missingOptionKeys(): string[];
54
+ /** Full selection resolution (the C1 amendment): true when every required
55
+ * attribute is selected AND the selection matches a valid combination. */
56
+ selectionValid(): boolean;
57
+ /** The variant (gvid) the current selection resolves to. Falls back to the
58
+ * product's initial `variantId` while nothing has been selected. */
59
+ resolvedVariantId(): string | undefined;
60
+ /** Price of the resolved combination, when the catalog carries one. */
61
+ resolvedPrice(): number | undefined;
62
+ /** Choices invalid given the rest of the current selection — for hosts to
63
+ * disable/grey out in their variant UI. */
64
+ disabledChoices(): Record<string, string[]>;
65
+ /** The combinations (for payload assembly via `createCartDetail`). */
66
+ getCombinations(): Combination[];
67
+ private bump;
68
+ }
@@ -36,6 +36,10 @@ export type { EditorTextFieldProps } from './EditorTextField.js';
36
36
  export type { EditorBuyProps, EditorBuyPayload, BuyMissingReason } from './EditorBuy.js';
37
37
  export type { DesignState } from './designStateStore.js';
38
38
  export { DesignStateStore } from './designStateStore.js';
39
+ export { useProductSelection } from './useProductSelection.js';
40
+ export type { ProductSelectionApi } from './useProductSelection.js';
41
+ export { CommerceStateStore } from './commerceStore.js';
42
+ export type { ProductOptionChoice, ProductSelectionAttribute } from './commerceStore.js';
39
43
  export type { EditorLayersProps } from './EditorLayers.js';
40
44
  export type { EditorControlsProps } from './EditorControls.js';
41
45
  export type { EditorHistoryProps } from './EditorHistory.js';
@@ -0,0 +1,39 @@
1
+ import { OptionSelection } from '@snowcone-app/sdk';
2
+ import { ProductSelectionAttribute } from './commerceStore.js';
3
+ export interface ProductSelectionApi {
4
+ /** The product's variant attributes (normalized catalog `options.attributes`).
5
+ * Choices are ALWAYS `{ label, hex?, … }` OBJECTS — render `choice.label`,
6
+ * use `hex` for color swatches. Empty object for an option-less product. */
7
+ attributes: Record<string, ProductSelectionAttribute>;
8
+ /** Current choices BY LABEL STRING, e.g. `{ Color: 'Black', Size: 'M' }` —
9
+ * the label is the currency everywhere selection is read or written.
10
+ * Seeded from the Root product's `variantId` combination when present. */
11
+ selection: OptionSelection;
12
+ /** Select a choice by its LABEL string (`select('Color', choice.label)`).
13
+ * Unknown key/choice → throws `E_UNKNOWN_OPTION` listing the valid ones. */
14
+ select: (key: string, choice: string) => void;
15
+ /** Choice LABELS invalid given the rest of the selection — disable these. */
16
+ disabledChoices: Record<string, string[]>;
17
+ /** The variant (gvid) the selection resolves to; `undefined` while
18
+ * incomplete/invalid. This is what renders and what Buy submits. */
19
+ variantId: string | undefined;
20
+ /** Resolved combination's price, when the catalog carries one. */
21
+ price: number | undefined;
22
+ /** Buy-owned quantity (completeness rule C5 gates on it). */
23
+ quantity: number;
24
+ /** Set the Buy-owned quantity. Values that are not integers ≥ 1 are stored
25
+ * as-is and GATE Buy with the `quantity` reason code (user state, never a
26
+ * throw) — clamp in your stepper if you never want that state. */
27
+ setQuantity: (quantity: number) => void;
28
+ /** True when every required attribute is selected and the combination is
29
+ * valid — the C1 component of Buy's completeness. */
30
+ complete: boolean;
31
+ /** Required attribute keys still unselected (Buy's `option:<key>` codes). */
32
+ missing: string[];
33
+ }
34
+ /**
35
+ * Read/write the Buy-owned product selection under this `Editor.Root`.
36
+ *
37
+ * @experimental ADR-0085; subject to change until the P4 gate.
38
+ */
39
+ export declare function useProductSelection(): ProductSelectionApi;
@@ -0,0 +1,343 @@
1
+ import { g as l } from "./EditorContext-5QpqAhjh.js";
2
+ const k = 1;
3
+ function d(t) {
4
+ return typeof t == "number" && Number.isFinite(t) ? Math.round(t * 100) / 100 : t;
5
+ }
6
+ function p(t) {
7
+ return {
8
+ ...t,
9
+ ...t.x !== void 0 && { x: d(t.x) },
10
+ ...t.y !== void 0 && { y: d(t.y) },
11
+ ...t.width !== void 0 && { width: d(t.width) },
12
+ ...t.height !== void 0 && { height: d(t.height) },
13
+ ...t.rotation !== void 0 && { rotation: d(t.rotation) }
14
+ };
15
+ }
16
+ function x(t) {
17
+ var a;
18
+ const i = (a = t.artboards) != null && a.length ? t.artboards : [{ name: "Front", width: 800, height: 800 }], e = i.map(() => []);
19
+ for (const r of t.elements || []) {
20
+ const o = r == null ? void 0 : r.artboard, s = typeof o == "string" ? i.findIndex((n) => (n.name || "Front") === o) : -1;
21
+ e[s >= 0 ? s : 0].push(R(r));
22
+ }
23
+ return {
24
+ schemaVersion: k,
25
+ artboards: i.map((r, o) => ({
26
+ id: `artboard-${o + 1}`,
27
+ name: r.name || "Front",
28
+ x: 0,
29
+ y: 0,
30
+ width: d(r.width || 800),
31
+ height: d(r.height || 800),
32
+ backgroundColor: r.backgroundColor || "transparent",
33
+ // Pass the artboard's flag through — the server worker only paints the
34
+ // background when this is true. Hardcoding `false` here silently dropped
35
+ // explicitly-set artboard backgrounds from every realtime mockup while
36
+ // the editor kept showing them (WYSIWYG drift).
37
+ exportBackground: r.exportBackground ?? !1,
38
+ elements: e[o],
39
+ distressTexture: r.distressTexture,
40
+ imageMask: r.imageMask
41
+ }))
42
+ };
43
+ }
44
+ function R(t) {
45
+ const i = t.type || t.transformType;
46
+ if (i === "image")
47
+ return C(t);
48
+ const { artboard: e, ...a } = t;
49
+ return p({
50
+ ...a,
51
+ type: i
52
+ });
53
+ }
54
+ function C(t) {
55
+ var c;
56
+ const i = t.transformData || {}, e = t.masks && t.masks.length > 0, a = i.width || 0, r = i.height || 0, o = e ? a : a * (i.cropWidth ?? 1), s = e ? r : r * (i.cropHeight ?? 1), n = {
57
+ id: t.id,
58
+ type: "image",
59
+ x: t.x || 0,
60
+ y: t.y || 0,
61
+ width: o,
62
+ height: s,
63
+ rotation: t.rotation || 0,
64
+ imageUrl: t.imageUrl,
65
+ imageAspectRatio: t.imageAspectRatio
66
+ };
67
+ return !e && (i.cropX !== 0 || i.cropY !== 0 || i.cropWidth !== 1 || i.cropHeight !== 1) && (n.cropX = i.cropX, n.cropY = i.cropY, n.cropWidth = i.cropWidth, n.cropHeight = i.cropHeight, n.needsCropPixelConversion = !0), i.flipHorizontal && (n.flipHorizontal = i.flipHorizontal), i.flipVertical && (n.flipVertical = i.flipVertical), i.borderRadius && (n.borderRadius = i.borderRadius), t.opacity !== void 0 && (n.opacity = t.opacity), t.distressEffect && (n.distressEffect = t.distressEffect), ((c = t.masks) == null ? void 0 : c.length) > 0 && (n.masks = t.masks), t.blendMode && (n.blendMode = t.blendMode), t.knockoutParts && (n.knockoutParts = t.knockoutParts), t.stroke && (n.stroke = t.stroke), p(n);
68
+ }
69
+ const u = "center", y = Symbol.for("snowcone.canvas.layerToken");
70
+ function S(t) {
71
+ return typeof t == "object" && t !== null && t[y] === !0 && typeof t.name == "string" && (t.kind === "text" || t.kind === "image");
72
+ }
73
+ function N(t, i) {
74
+ return Object.freeze({ kind: t, name: i, [y]: !0 });
75
+ }
76
+ function m(t) {
77
+ const i = t.transformType;
78
+ return i === "image" ? "image" : i === "shape" || i === "group" || i === "path" ? null : "text";
79
+ }
80
+ function T(t) {
81
+ return t === "top-left" || t === "left" || t === "bottom-left" ? "left" : t === "top-right" || t === "right" || t === "bottom-right" ? "right" : "center";
82
+ }
83
+ function E(t) {
84
+ return t === "top-left" || t === "top" || t === "top-right" ? "top" : t === "bottom-left" || t === "bottom" || t === "bottom-right" ? "bottom" : "middle";
85
+ }
86
+ function f(t, i, e, a, r, o) {
87
+ const s = T(t), n = E(t), h = s === "left" ? i / 2 + o : s === "right" ? a - i / 2 - o : a / 2, c = n === "top" ? e / 2 + o : n === "bottom" ? r - e / 2 - o : r / 2;
88
+ return { x: h, y: c };
89
+ }
90
+ function H(t, i, e) {
91
+ const a = i / e;
92
+ if (t >= a) {
93
+ const s = e * t, n = i / s;
94
+ return {
95
+ width: s,
96
+ height: e,
97
+ cropX: (1 - n) / 2,
98
+ cropY: 0,
99
+ cropWidth: n,
100
+ cropHeight: 1
101
+ };
102
+ }
103
+ const r = i / t, o = e / r;
104
+ return {
105
+ width: i,
106
+ height: r,
107
+ cropX: 0,
108
+ cropY: (1 - o) / 2,
109
+ cropWidth: 1,
110
+ cropHeight: o
111
+ };
112
+ }
113
+ class g {
114
+ constructor(i) {
115
+ if (this.els = [], this.idCounter = 0, !(i.width > 0) || !(i.height > 0))
116
+ throw new Error("design(): artboard width and height must be positive numbers");
117
+ this.artboard = {
118
+ name: i.name || "Front",
119
+ width: i.width,
120
+ height: i.height,
121
+ backgroundColor: i.backgroundColor
122
+ };
123
+ }
124
+ /**
125
+ * Seed a builder from a previously saved canvas state — the editor shape
126
+ * `<SnowconeCanvas onChange>` emits (and `toJSON()` produces).
127
+ *
128
+ * This is how a saved editable template re-enters the turnkey embed path:
129
+ * load the template's state JSON, lift it back into a `DesignBuilder`, and
130
+ * hand it to `<RealtimeEmbed design={…}>`. The active artboard becomes the
131
+ * builder's artboard; elements tagged for other artboards are skipped
132
+ * (untagged elements are assumed to belong to the active one).
133
+ */
134
+ static fromState(i) {
135
+ const e = i.artboards.find((r) => r.name === i.activeArtboard) ?? i.artboards[0];
136
+ if (!e)
137
+ throw new Error("DesignBuilder.fromState(): state has no artboards");
138
+ const a = new g({
139
+ name: e.name,
140
+ width: e.width,
141
+ height: e.height,
142
+ backgroundColor: e.backgroundColor
143
+ });
144
+ for (const r of i.elements)
145
+ r.artboard && r.artboard !== e.name || a.els.push({ ...r });
146
+ return a;
147
+ }
148
+ nextId(i) {
149
+ return `${i}-${++this.idCounter}`;
150
+ }
151
+ background(i, e = {}) {
152
+ const { width: a, height: r } = this.artboard;
153
+ let o;
154
+ if (e.aspectRatio && e.aspectRatio > 0) {
155
+ const n = H(e.aspectRatio, a, r);
156
+ o = {
157
+ type: "image",
158
+ width: n.width,
159
+ height: n.height,
160
+ cropX: n.cropX,
161
+ cropY: n.cropY,
162
+ cropWidth: n.cropWidth,
163
+ cropHeight: n.cropHeight,
164
+ flipHorizontal: !1,
165
+ flipVertical: !1,
166
+ borderRadius: 0
167
+ };
168
+ } else
169
+ o = {
170
+ type: "image",
171
+ width: a,
172
+ height: r,
173
+ cropX: 0,
174
+ cropY: 0,
175
+ cropWidth: 1,
176
+ cropHeight: 1,
177
+ flipHorizontal: !1,
178
+ flipVertical: !1,
179
+ borderRadius: 0
180
+ };
181
+ const s = {
182
+ id: this.nextId("bg"),
183
+ transformType: "image",
184
+ x: a / 2,
185
+ y: r / 2,
186
+ rotation: 0,
187
+ opacity: e.opacity ?? 1,
188
+ imageUrl: i,
189
+ imageAspectRatio: e.aspectRatio,
190
+ transformData: o,
191
+ name: e.name ?? "Background"
192
+ };
193
+ return this.els.unshift(s), this;
194
+ }
195
+ image(i, e = {}) {
196
+ const { width: a, height: r } = this.artboard, o = e.width ?? Math.round(a / 3), s = e.height ?? (e.aspectRatio ? Math.round(o / e.aspectRatio) : o), { x: n, y: h } = f(e.anchor ?? u, o, s, a, r, e.padding ?? 0), c = {
197
+ id: this.nextId("img"),
198
+ transformType: "image",
199
+ x: n,
200
+ y: h,
201
+ rotation: e.rotation ?? 0,
202
+ opacity: e.opacity ?? 1,
203
+ imageUrl: i,
204
+ imageAspectRatio: e.aspectRatio,
205
+ transformData: {
206
+ type: "image",
207
+ width: o,
208
+ height: s,
209
+ cropX: 0,
210
+ cropY: 0,
211
+ cropWidth: 1,
212
+ cropHeight: 1,
213
+ flipHorizontal: !1,
214
+ flipVertical: !1,
215
+ borderRadius: 0
216
+ },
217
+ ...e.name ? { name: e.name } : {}
218
+ };
219
+ return this.els.push(c), this;
220
+ }
221
+ text(i, e = {}) {
222
+ const { width: a, height: r } = this.artboard, o = e.fontSize ?? Math.round(this.artboard.height / 12), { x: s, y: n } = f(e.anchor ?? "top", 0, o, a, r, e.padding ?? Math.round(r / 20)), h = {
223
+ id: this.nextId("text"),
224
+ transformType: "custom",
225
+ x: s,
226
+ y: n,
227
+ rotation: e.rotation ?? 0,
228
+ text: i,
229
+ fontSize: o,
230
+ fontFamily: e.fontFamily ?? "Anton",
231
+ color: e.color ?? "#ffffff",
232
+ textAlign: e.textAlign ?? "center",
233
+ bold: e.bold ?? !1,
234
+ italic: e.italic ?? !1,
235
+ transformData: { type: "custom" },
236
+ ...e.name ? { name: e.name } : {}
237
+ };
238
+ return this.els.push(h), this;
239
+ }
240
+ /**
241
+ * Mint a {@link LayerToken} for a named layer — the only currency
242
+ * `Editor.TextField` accepts (ADR-0085 §1). Compile time: `name` is
243
+ * constrained to this design's declared layer names, kind inferred from
244
+ * which per-kind union it falls in. Runtime: validated EAGERLY against the
245
+ * built elements (nearest the mistake), so CMS/JSON-loaded designs get the
246
+ * same protection with `string` typing.
247
+ *
248
+ * @throws EditorError `E_LAYER_NOT_FOUND` — no layer with this name; lists
249
+ * the valid names per kind.
250
+ * @throws EditorError `E_LAYER_WRONG_KIND` — the name matches a layer that
251
+ * is not bindable (shape/group/path).
252
+ * @experimental ADR-0085; subject to change until the P4 gate.
253
+ */
254
+ layer(i) {
255
+ const e = this.els.find((r) => r.name === i);
256
+ if (!e) {
257
+ const r = this.layerNamesOfKind("text"), o = this.layerNamesOfKind("image");
258
+ throw new l(
259
+ "E_LAYER_NOT_FOUND",
260
+ `no layer "${i}" in this design. Text layers: ${JSON.stringify(r)}; image layers: ${JSON.stringify(o)}. Fix the name, or add the layer first — e.g. design(…).text('…', { name: '${i}' }).`,
261
+ { layerName: i, validNames: [...r, ...o], textNames: r, imageNames: o, docSlug: "embed-mode" }
262
+ );
263
+ }
264
+ const a = m(e);
265
+ if (!a)
266
+ throw new l(
267
+ "E_LAYER_WRONG_KIND",
268
+ `layer "${i}" is a ${e.transformType} element — only text and image layers are bindable. Bindable layers: ${JSON.stringify([...this.layerNamesOfKind("text"), ...this.layerNamesOfKind("image")])}.`,
269
+ { layerName: i, actualType: e.transformType, docSlug: "embed-mode" }
270
+ );
271
+ return N(a, i);
272
+ }
273
+ /** Names of bindable layers of one kind, in stack order. @internal */
274
+ layerNamesOfKind(i) {
275
+ return this.els.filter((e) => m(e) === i).map((e) => e.name).filter((e) => !!e);
276
+ }
277
+ /** An anchored vector shape. */
278
+ shape(i, e = {}) {
279
+ const { width: a, height: r } = this.artboard, o = e.size ?? Math.round(a / 6), s = typeof o == "number" ? o : o.width, n = typeof o == "number" ? o : o.height, { x: h, y: c } = f(e.anchor ?? u, s, n, a, r, e.padding ?? 0), b = {
280
+ type: "shape",
281
+ shapeType: i,
282
+ width: s,
283
+ height: n,
284
+ fillColor: e.fill ?? "#000000",
285
+ fillOpacity: e.opacity ?? 1,
286
+ ...e.points !== void 0 ? { points: e.points } : {},
287
+ ...e.sides !== void 0 ? { sides: e.sides } : {}
288
+ }, w = {
289
+ id: this.nextId("shape"),
290
+ transformType: "shape",
291
+ x: h,
292
+ y: c,
293
+ rotation: e.rotation ?? 0,
294
+ transformData: b,
295
+ ...e.name ? { name: e.name } : {}
296
+ };
297
+ return this.els.push(w), this;
298
+ }
299
+ /** The composed elements, in render order (background first). Editor shape. */
300
+ get elements() {
301
+ return this.els.map((i) => ({ ...i }));
302
+ }
303
+ /** Editor-shape canvas state for `<SnowconeCanvas initialElements=… />` and
304
+ * `serializeStateForServer`. `activeArtboard` is the artboard name. */
305
+ toJSON() {
306
+ return {
307
+ elements: this.elements,
308
+ artboards: [{
309
+ name: this.artboard.name,
310
+ width: this.artboard.width,
311
+ height: this.artboard.height,
312
+ // A background set on a design is part of the design: flag it
313
+ // renderable so exports and server renders paint it, matching the
314
+ // editor (the export worker skips it without `exportBackground`).
315
+ ...this.artboard.backgroundColor ? {
316
+ backgroundColor: this.artboard.backgroundColor,
317
+ exportBackground: this.artboard.backgroundColor !== "transparent"
318
+ } : {}
319
+ }],
320
+ activeArtboard: this.artboard.name
321
+ };
322
+ }
323
+ /** Server-render payload for `RenderSession.renderState(placement, …)`.
324
+ * Equivalent to `serializeStateForServer(design.toJSON())`. */
325
+ toServerRequest() {
326
+ return x(this.toJSON());
327
+ }
328
+ }
329
+ function v(t) {
330
+ return new g(t);
331
+ }
332
+ function z(t) {
333
+ return m(t);
334
+ }
335
+ export {
336
+ k as C,
337
+ g as D,
338
+ v as d,
339
+ S as i,
340
+ z as l,
341
+ x as s
342
+ };
343
+ //# sourceMappingURL=design-builder-v4935TeS.js.map