@snowcone-app/canvas 0.21.3 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/STYLING_CONTRACT.md +131 -92
- package/dist/{EditorCanvasImpl-M7rw1c1k.js → EditorCanvasImpl-Ckg7WO2j.js} +4 -4
- package/dist/{EditorCanvasImpl-M7rw1c1k.js.map → EditorCanvasImpl-Ckg7WO2j.js.map} +1 -1
- package/dist/{SnowconeCanvas-Cx7nXlBI.js → SnowconeCanvas-D1sLcIja.js} +2 -2
- package/dist/{SnowconeCanvas-Cx7nXlBI.js.map → SnowconeCanvas-D1sLcIja.js.map} +1 -1
- package/dist/api/embed.d.ts +5 -3
- package/dist/components/editor/EditorCanvas.d.ts +9 -0
- package/dist/components/editor/EditorRoot.d.ts +11 -0
- package/dist/components/embed-realtime/validateEmbed.d.ts +10 -4
- package/dist/design/design-builder.d.ts +67 -12
- package/dist/design/product-config.d.ts +48 -0
- package/dist/design-builder-2VUjfmdt.js +422 -0
- package/dist/design-builder-2VUjfmdt.js.map +1 -0
- package/dist/editorErrors-B2mb1sVN.js.map +1 -1
- package/dist/{embed-QBZkgWDr.js → embed-DxsaqZJF.js} +524 -430
- package/dist/embed-DxsaqZJF.js.map +1 -0
- package/dist/embed.mjs +31 -29
- package/dist/errors/editorError.d.ts +13 -0
- package/dist/hooks/bindingGuards.d.ts +16 -0
- package/dist/hooks/useColorBinding.d.ts +2 -1
- package/dist/hooks/useFontBinding.d.ts +2 -1
- package/dist/hooks/useImageBinding.d.ts +2 -1
- package/dist/hooks/useTextBinding.d.ts +2 -1
- package/dist/index.mjs +3 -3
- package/dist/template-fields-CQ6n2P4V.js +548 -0
- package/dist/template-fields-CQ6n2P4V.js.map +1 -0
- package/dist/testing/failureFixtures.d.ts +2 -2
- package/dist/testing.mjs +1 -1
- package/example/package.json +1 -1
- package/package.json +1 -1
- package/dist/design-builder-CT4DHIPe.js +0 -367
- package/dist/design-builder-CT4DHIPe.js.map +0 -1
- package/dist/embed-QBZkgWDr.js.map +0 -1
- package/dist/template-fields-C2pSfMBh.js +0 -494
- package/dist/template-fields-C2pSfMBh.js.map +0 -1
package/dist/api/embed.d.ts
CHANGED
|
@@ -49,8 +49,10 @@ export { useRealtimeMockups } from '../components/embed-realtime/useRealtimeMock
|
|
|
49
49
|
export type { UseRealtimeMockupsResult } from '../components/embed-realtime/useRealtimeMockups.js';
|
|
50
50
|
export { validateEmbed, collectEmbedIssues } from '../components/embed-realtime/validateEmbed.js';
|
|
51
51
|
export type { ValidateEmbedInput, EmbedIssue } from '../components/embed-realtime/validateEmbed.js';
|
|
52
|
-
export {
|
|
53
|
-
export type {
|
|
52
|
+
export { productToRenderConfig } from '../design/product-config.js';
|
|
53
|
+
export type { ProductRenderConfig, ProductRenderConfigOptions } from '../design/product-config.js';
|
|
54
|
+
export { design, DesignBuilder, isLayerToken, isColorToken } from '../design/design-builder.js';
|
|
55
|
+
export type { Anchor, ShapeKind, DesignArtboard, StateArtboard, FromStateOptions, AnyDesign, LayerKind, LayerToken, ColorToken, } from '../design/design-builder.js';
|
|
54
56
|
export { useEditor } from '../contexts/EditorContext.js';
|
|
55
57
|
export type { EditorContextValue, EditorProviderProps } from '../contexts/EditorContext.js';
|
|
56
58
|
export { useLayers } from '../hooks/useLayers.js';
|
|
@@ -77,6 +79,6 @@ export { CanvasServicesProvider, useCanvasServices } from '../contexts/CanvasSer
|
|
|
77
79
|
export type { CanvasServices, ResolvedCanvasServices } from '../contexts/CanvasServicesContext.js';
|
|
78
80
|
export { createProxyImageSearch, createProxyGenerate } from '../services/imageSearch.js';
|
|
79
81
|
export type { ImageSearchProvider, ImageSearchResult, ImageSearchResponse, ImageSearchParams, GenerateProvider, GenerateParams, GenerateResponse, } from '../services/imageSearch.js';
|
|
80
|
-
export type { RenderProduct, RenderProductOptions, RenderProductCombination, RenderProductRequiredPlacement, MockupResult, RealtimeGrant, } from '@snowcone-app/sdk';
|
|
82
|
+
export type { CatalogProduct, RenderProduct, RenderProductOptions, RenderProductCombination, RenderProductRequiredPlacement, MockupResult, RealtimeGrant, } from '@snowcone-app/sdk';
|
|
81
83
|
export { SnowconeEditor, formatBuyPrice } from '../default-editor/SnowconeEditor.js';
|
|
82
84
|
export type { SnowconeEditorProps } from '../default-editor/SnowconeEditor.js';
|
|
@@ -16,6 +16,15 @@ export interface EditorCanvasProps {
|
|
|
16
16
|
children?: ReactNode;
|
|
17
17
|
/** Cap the canvas height in px. */
|
|
18
18
|
maxHeight?: number;
|
|
19
|
+
/**
|
|
20
|
+
* The canvas's built-in selection-anchored floating toolbar (default
|
|
21
|
+
* `true`). Pass `false` when your composition owns the contextual zone —
|
|
22
|
+
* e.g. a conditionally-rendered `<Editor.Controls placement="docked"/>` —
|
|
23
|
+
* so suppression is declared HERE, in the type surface, rather than implied
|
|
24
|
+
* by whether a docked Controls happens to be mounted this frame (a mounted
|
|
25
|
+
* docked Controls still suppresses it either way, ADR-0085 §5).
|
|
26
|
+
*/
|
|
27
|
+
floatingToolbar?: boolean;
|
|
19
28
|
className?: string;
|
|
20
29
|
style?: CSSProperties;
|
|
21
30
|
}
|
|
@@ -2,6 +2,7 @@ import { default as React, ReactNode } from 'react';
|
|
|
2
2
|
import { RenderProduct, MockupResult, RealtimeGrant } from '@snowcone-app/sdk';
|
|
3
3
|
import { AnyDesign } from '../../design/design-builder.js';
|
|
4
4
|
import { CanvasServices } from '../../contexts/CanvasServicesContext.js';
|
|
5
|
+
import { EmbedIssue } from '../embed-realtime/validateEmbed.js';
|
|
5
6
|
/**
|
|
6
7
|
* How the session authorizes. Provide exactly one — the discriminated union makes
|
|
7
8
|
* "I forgot the grant" a compile error rather than a silent runtime hang.
|
|
@@ -41,6 +42,16 @@ export type EditorRootBaseProps = {
|
|
|
41
42
|
onMockups?: (results: MockupResult[]) => void;
|
|
42
43
|
/** Called on a render/timeout error. */
|
|
43
44
|
onRenderError?: (message: string) => void;
|
|
45
|
+
/**
|
|
46
|
+
* The machine-readable advisory channel (F3's other half): every non-fatal
|
|
47
|
+
* issue — warn-severity wiring problems at mount, the runtime `slow-render`
|
|
48
|
+
* stall — is delivered here, in dev AND prod. Console output is as
|
|
49
|
+
* invisible to a headless agent as it is to production telemetry; assert on
|
|
50
|
+
* this callback instead of scraping logs. (Error-severity wiring issues
|
|
51
|
+
* don't arrive here — they throw `EditorError E_EMBED_CONFIG` into Root's
|
|
52
|
+
* boundary.)
|
|
53
|
+
*/
|
|
54
|
+
onIssue?: (issue: EmbedIssue) => void;
|
|
44
55
|
/** Override the realtime WS endpoint (defaults to the SDK's prod URL). */
|
|
45
56
|
wsUrl?: string;
|
|
46
57
|
/**
|
|
@@ -6,13 +6,19 @@ export interface ValidateEmbedInput {
|
|
|
6
6
|
design: AnyDesign;
|
|
7
7
|
hasGrant: boolean;
|
|
8
8
|
}
|
|
9
|
-
/** One wiring problem found at mount. `error`-severity
|
|
10
|
-
* (no render will ever arrive / it targets the wrong
|
|
11
|
-
* issues are advisory.
|
|
9
|
+
/** One wiring problem found at mount, or a runtime stall. `error`-severity
|
|
10
|
+
* issues break the embed (no render will ever arrive / it targets the wrong
|
|
11
|
+
* thing); `warn`-severity issues are advisory. Every issue also reaches the
|
|
12
|
+
* host through `Editor.Root`'s `onIssue` — the machine-readable channel
|
|
13
|
+
* (console.warn is as invisible to a headless agent as console.error). */
|
|
12
14
|
export interface EmbedIssue {
|
|
13
15
|
severity: 'error' | 'warn';
|
|
14
16
|
/** Stable issue key, carried into `EditorError.data.issue` in throw mode. */
|
|
15
|
-
issue: 'missing-shop' | 'missing-grant' | 'empty-mockup-ids' | 'variant-required' | 'multi-placement' | 'placement-mismatch' | 'duplicate-layer-names'
|
|
17
|
+
issue: 'missing-shop' | 'missing-grant' | 'empty-mockup-ids' | 'variant-required' | 'multi-placement' | 'placement-mismatch' | 'duplicate-layer-names'
|
|
18
|
+
/** Runtime (not mount-time): a render has been in flight past
|
|
19
|
+
* `SLOW_RENDER_HINT_MS` with no mockup and no error — the silent-hang
|
|
20
|
+
* class. The render may still arrive; this is a heads-up. */
|
|
21
|
+
| 'slow-render';
|
|
16
22
|
message: string;
|
|
17
23
|
}
|
|
18
24
|
/** Find every wiring problem. Pure — no logging, no throwing. */
|
|
@@ -12,8 +12,18 @@ export interface DesignArtboard {
|
|
|
12
12
|
width: number;
|
|
13
13
|
height: number;
|
|
14
14
|
/** Optional solid background fill behind all elements. Rendered everywhere
|
|
15
|
-
* the design is — editor, exports, and server renders (realtime mockups).
|
|
16
|
-
|
|
15
|
+
* the design is — editor, exports, and server renders (realtime mockups).
|
|
16
|
+
* NOTE: the background is not an element, so it is NOT repaintable by
|
|
17
|
+
* `useColorBinding` — designs that want a rebindable backdrop paint a
|
|
18
|
+
* full-bleed `shape('rectangle', { fill: … })` instead. */
|
|
19
|
+
backgroundColor?: string | ColorToken;
|
|
20
|
+
/** Named color ROLES for value-matched rebinding (`useColorBinding`).
|
|
21
|
+
* Mint tokens with `design.color('role')`; use them as `fill`/`color`
|
|
22
|
+
* values so role and element can never drift. Validated at mint:
|
|
23
|
+
* duplicate values across roles throw `E_COLOR_ROLE_COLLISION` (a
|
|
24
|
+
* value-matched binding can't tell colliding roles apart), non-hex
|
|
25
|
+
* values throw `E_COLOR_ROLE_INVALID`. */
|
|
26
|
+
colors?: Record<string, string>;
|
|
17
27
|
/** Pacing role in a multi-page template (ADR-0086 albums). Template
|
|
18
28
|
* metadata only — never sent on the server render wire. */
|
|
19
29
|
role?: ArtboardRole;
|
|
@@ -71,7 +81,8 @@ export interface TextOptions extends NamedOption {
|
|
|
71
81
|
anchor?: Anchor;
|
|
72
82
|
fontSize?: number;
|
|
73
83
|
fontFamily?: string;
|
|
74
|
-
color
|
|
84
|
+
/** Hex string, or a `design.color('role')` token (resolved at build). */
|
|
85
|
+
color?: string | ColorToken;
|
|
75
86
|
/** Horizontal text alignment. Named `textAlign` to match the canvas editor's
|
|
76
87
|
* element field (not `align`), so the builder and the editor read the same. */
|
|
77
88
|
textAlign?: 'left' | 'center' | 'right';
|
|
@@ -87,7 +98,8 @@ export interface ShapeOptions extends NamedOption {
|
|
|
87
98
|
width: number;
|
|
88
99
|
height: number;
|
|
89
100
|
};
|
|
90
|
-
|
|
101
|
+
/** Hex string, or a `design.color('role')` token (resolved at build). */
|
|
102
|
+
fill?: string | ColorToken;
|
|
91
103
|
rotation?: number;
|
|
92
104
|
opacity?: number;
|
|
93
105
|
/** star points / polygon sides. */
|
|
@@ -122,6 +134,30 @@ export interface LayerToken<K extends LayerKind = LayerKind, N extends string =
|
|
|
122
134
|
}
|
|
123
135
|
/** Runtime check used by parts before trusting a `layer` prop. */
|
|
124
136
|
export declare function isLayerToken(value: unknown): value is LayerToken;
|
|
137
|
+
declare const COLOR_TOKEN_BRAND: unique symbol;
|
|
138
|
+
/**
|
|
139
|
+
* A validated claim that color role `N` exists in the design that minted it,
|
|
140
|
+
* carrying the role's (normalized) hex value. `useColorBinding` accepts it in
|
|
141
|
+
* place of a bare hex, so the chip and the painted elements can never drift —
|
|
142
|
+
* and `design({ colors })` rejects role-value collisions at mint time, which a
|
|
143
|
+
* value-matched binding cannot survive (one role's repaint would capture the
|
|
144
|
+
* other role's elements). Mint with {@link DesignBuilder.color}; not
|
|
145
|
+
* constructible by hand (branded).
|
|
146
|
+
*/
|
|
147
|
+
export interface ColorToken<N extends string = string> {
|
|
148
|
+
readonly name: N;
|
|
149
|
+
/** Normalized uppercase `#RRGGBB`. */
|
|
150
|
+
readonly value: string;
|
|
151
|
+
/** @internal */
|
|
152
|
+
readonly [COLOR_TOKEN_BRAND]: true;
|
|
153
|
+
}
|
|
154
|
+
/** Runtime check used by `useColorBinding` (and the builder's value slots)
|
|
155
|
+
* before trusting a color value. */
|
|
156
|
+
export declare function isColorToken(value: unknown): value is ColorToken;
|
|
157
|
+
/** Normalize `#RGB`/`#RRGGBB` (any case) to uppercase `#RRGGBB` — the same
|
|
158
|
+
* normalization `useColorBinding` matches with. Returns null when the value
|
|
159
|
+
* is not a parsable hex color. */
|
|
160
|
+
export declare function normalizeRoleHex(color: string): string | null;
|
|
125
161
|
/** The token kind inferred for name `N` against the builder's accumulated
|
|
126
162
|
* per-kind name unions. A dynamically-loaded design (both unions degraded to
|
|
127
163
|
* `string`) yields the full `'text' | 'image'` union — typing degrades, the
|
|
@@ -141,9 +177,11 @@ type InferKind<N extends string, TText extends string, TImage extends string> =
|
|
|
141
177
|
* API, runtime-validated instead (the degradation is deliberate and LOUD in
|
|
142
178
|
* docs; never block the dynamic case).
|
|
143
179
|
*/
|
|
144
|
-
export declare class DesignBuilder<out TText extends string = string, out TImage extends string = string> {
|
|
180
|
+
export declare class DesignBuilder<out TText extends string = string, out TImage extends string = string, out TColor extends string = string> {
|
|
145
181
|
private readonly artboard;
|
|
146
182
|
private readonly els;
|
|
183
|
+
/** Declared color roles, name → normalized uppercase `#RRGGBB`. */
|
|
184
|
+
private readonly colorRoles;
|
|
147
185
|
private idCounter;
|
|
148
186
|
/** Every artboard of a multi-page state (fromState `{ artboards: 'all' }`);
|
|
149
187
|
* unset for single-artboard builders. */
|
|
@@ -177,19 +215,19 @@ export declare class DesignBuilder<out TText extends string = string, out TImage
|
|
|
177
215
|
* not one union with text — ADR-0085 §1). */
|
|
178
216
|
background<const N extends string>(imageUrl: string, opts: BackgroundOptions & {
|
|
179
217
|
name: N;
|
|
180
|
-
}): DesignBuilder<TText, TImage | N>;
|
|
181
|
-
background(imageUrl: string, opts?: BackgroundOptions): DesignBuilder<TText, TImage | 'Background'>;
|
|
218
|
+
}): DesignBuilder<TText, TImage | N, TColor>;
|
|
219
|
+
background(imageUrl: string, opts?: BackgroundOptions): DesignBuilder<TText, TImage | 'Background', TColor>;
|
|
182
220
|
/** An anchored image element (not full-bleed). */
|
|
183
221
|
image<const N extends string>(imageUrl: string, opts: ImageOptions & {
|
|
184
222
|
name: N;
|
|
185
|
-
}): DesignBuilder<TText, TImage | N>;
|
|
186
|
-
image(imageUrl: string, opts?: ImageOptions): DesignBuilder<TText, TImage>;
|
|
223
|
+
}): DesignBuilder<TText, TImage | N, TColor>;
|
|
224
|
+
image(imageUrl: string, opts?: ImageOptions): DesignBuilder<TText, TImage, TColor>;
|
|
187
225
|
/** An anchored text layer. Horizontal centring is exact (textAlign); vertical
|
|
188
226
|
* anchoring uses the font size as the line-height estimate. */
|
|
189
227
|
text<const N extends string>(text: string, opts: TextOptions & {
|
|
190
228
|
name: N;
|
|
191
|
-
}): DesignBuilder<TText | N, TImage>;
|
|
192
|
-
text(text: string, opts?: TextOptions): DesignBuilder<TText, TImage>;
|
|
229
|
+
}): DesignBuilder<TText | N, TImage, TColor>;
|
|
230
|
+
text(text: string, opts?: TextOptions): DesignBuilder<TText, TImage, TColor>;
|
|
193
231
|
/**
|
|
194
232
|
* Mint a {@link LayerToken} for a named layer — the only currency
|
|
195
233
|
* `Editor.TextField` accepts (ADR-0085 §1). Compile time: `name` is
|
|
@@ -204,6 +242,20 @@ export declare class DesignBuilder<out TText extends string = string, out TImage
|
|
|
204
242
|
* is not bindable (shape/group/path).
|
|
205
243
|
*/
|
|
206
244
|
layer<N extends TText | TImage>(name: N): LayerToken<InferKind<N, TText, TImage>, N>;
|
|
245
|
+
/**
|
|
246
|
+
* Mint a {@link ColorToken} for a declared color role. Compile time: `name`
|
|
247
|
+
* is constrained to the roles declared in `design({ colors })`. Runtime:
|
|
248
|
+
* validated against the declared roles (CMS/JSON-built designs degrade to
|
|
249
|
+
* `string` typing and rely on this throw).
|
|
250
|
+
*
|
|
251
|
+
* Use the token wherever the design paints the role (`fill`, `color`) AND
|
|
252
|
+
* as the `useColorBinding` argument — one source of truth, no hand-synced
|
|
253
|
+
* hex pairs.
|
|
254
|
+
*
|
|
255
|
+
* @throws EditorError `E_COLOR_ROLE_NOT_FOUND` — no role with this name;
|
|
256
|
+
* lists the declared roles.
|
|
257
|
+
*/
|
|
258
|
+
color<N extends TColor>(name: N): ColorToken<N>;
|
|
207
259
|
/** Names of bindable layers of one kind, in stack order. @internal */
|
|
208
260
|
layerNamesOfKind(kind: LayerKind): string[];
|
|
209
261
|
/** An anchored vector shape. */
|
|
@@ -236,7 +288,10 @@ export declare class DesignBuilder<out TText extends string = string, out TImage
|
|
|
236
288
|
* // …or hand d.toJSON() to <SnowconeCanvas/> for interactive editing — the
|
|
237
289
|
* // `name`s let you bind layers with useImageBinding('art') / useTextBinding('headline').
|
|
238
290
|
*/
|
|
239
|
-
export declare function design(artboard: DesignArtboard
|
|
291
|
+
export declare function design<const C extends Record<string, string>>(artboard: DesignArtboard & {
|
|
292
|
+
colors: C;
|
|
293
|
+
}): DesignBuilder<never, never, Extract<keyof C, string>>;
|
|
294
|
+
export declare function design(artboard: DesignArtboard): DesignBuilder<never, never, never>;
|
|
240
295
|
/**
|
|
241
296
|
* Resolve a layer's kind against a live element/config list — the mount-time
|
|
242
297
|
* revalidation `Editor.TextField` runs (the token is a validated claim; the
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { CatalogProduct, RenderProduct } from '@snowcone-app/sdk';
|
|
2
|
+
export interface ProductRenderConfigOptions {
|
|
3
|
+
/** Placement label to edit (e.g. "Back"). Defaults to the product's first
|
|
4
|
+
* renderable image placement. Unknown labels throw with `validNames`. */
|
|
5
|
+
placement?: string;
|
|
6
|
+
/** Render width passthrough (`product.width`) — bump to ~2000 for a crisp
|
|
7
|
+
* full-bleed preview. `Editor.Preview` hints in dev when it's too low. */
|
|
8
|
+
width?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface ProductRenderConfig {
|
|
11
|
+
/** Ready for `<Editor.Root product={…}>` — advisory fields included so
|
|
12
|
+
* wiring mistakes fail loud at mount. */
|
|
13
|
+
product: RenderProduct;
|
|
14
|
+
/** The placement being edited — seed `design({ name: label, width, height })`
|
|
15
|
+
* from exactly these values. */
|
|
16
|
+
placement: {
|
|
17
|
+
label: string;
|
|
18
|
+
/** Artboard pixel dimensions (the placement's print-file geometry). */
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
/** width / height — for sizing the host's canvas stage. */
|
|
22
|
+
aspectRatio: number;
|
|
23
|
+
};
|
|
24
|
+
/** Every editable (non-color) placement label — placement pickers. */
|
|
25
|
+
placementLabels: string[];
|
|
26
|
+
/** First mockup scene that photographs the rendered variant — the default
|
|
27
|
+
* `<Editor.Preview mockupId>`. */
|
|
28
|
+
primaryMockupId: string;
|
|
29
|
+
/** The variant the config renders against (`undefined` when no color axis
|
|
30
|
+
* gates rendering). Already set on `product`. */
|
|
31
|
+
variantId: string | undefined;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Derive the full embed wiring from one `getProduct()` payload.
|
|
35
|
+
*
|
|
36
|
+
* ```ts
|
|
37
|
+
* const config = productToRenderConfig(await getProduct('BEEB77'));
|
|
38
|
+
* const seed = design({ name: config.placement.label, ...config.placement })
|
|
39
|
+
* .text('hello', { anchor: 'center', name: 'headline' });
|
|
40
|
+
*
|
|
41
|
+
* <Editor.Root shop={SHOP} product={config.product} design={seed} grantUrl="/api/realtime-grant">
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @throws EditorError `E_PRODUCT_NOT_RENDERABLE` when the product has no
|
|
45
|
+
* renderable image placement, no mockup scenes, or `options.placement`
|
|
46
|
+
* names a placement that doesn't exist (`data.validNames` lists the real ones).
|
|
47
|
+
*/
|
|
48
|
+
export declare function productToRenderConfig(catalogProduct: CatalogProduct, options?: ProductRenderConfigOptions): ProductRenderConfig;
|