@snowcone-app/canvas 0.27.0 → 0.29.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/ERROR_CONTRACT.md +13 -0
- package/dist/{CanvasRenderer-yszzrDfo.js → CanvasRenderer-_lFoIoz5.js} +916 -848
- package/dist/CanvasRenderer-_lFoIoz5.js.map +1 -0
- package/dist/{CanvasStateV1-BTbCD_xC.js → CanvasStateV1-BMxr6xb8.js} +7 -4
- package/dist/{CanvasStateV1-BTbCD_xC.js.map → CanvasStateV1-BMxr6xb8.js.map} +1 -1
- package/dist/ContextualToolbars-0jWvnQgp.js +5833 -0
- package/dist/ContextualToolbars-0jWvnQgp.js.map +1 -0
- package/dist/{EditorCanvasImpl-Ui8Ceb-h.js → EditorCanvasImpl-BNz8TPFs.js} +8 -8
- package/dist/{EditorCanvasImpl-Ui8Ceb-h.js.map → EditorCanvasImpl-BNz8TPFs.js.map} +1 -1
- package/dist/{ExportManager-BTcyj4cf.js → ExportManager-B8Td7uR5.js} +1051 -976
- package/dist/ExportManager-B8Td7uR5.js.map +1 -0
- package/dist/{FontAnalyzer-EF3gLPr8.js → FontAnalyzer-D4Sg-P6_.js} +1410 -1329
- package/dist/FontAnalyzer-D4Sg-P6_.js.map +1 -0
- package/dist/{ImagePanel-CIk6z41z.js → ImagePanel-CN2Bu3hf.js} +6 -6
- package/dist/{ImagePanel-CIk6z41z.js.map → ImagePanel-CN2Bu3hf.js.map} +1 -1
- package/dist/{SnowconeCanvas-DuWALOAt.js → SnowconeCanvas-CR_LdRFP.js} +27 -27
- package/dist/{SnowconeCanvas-DuWALOAt.js.map → SnowconeCanvas-CR_LdRFP.js.map} +1 -1
- package/dist/{UnifiedFontService-CHVYDBDW.js → UnifiedFontService-CgYnYTj7.js} +755 -713
- package/dist/UnifiedFontService-CgYnYTj7.js.map +1 -0
- package/dist/advanced.mjs +14 -14
- package/dist/components/MultiSelectionToolbar.d.ts +18 -0
- package/dist/components/toolbars/SecondaryPanels.d.ts +11 -0
- package/dist/components/ui/toolbar-button.d.ts +2 -0
- package/dist/core/GestureConstraints.d.ts +92 -0
- package/dist/core/InteractionStateMachine.d.ts +16 -2
- package/dist/core/ResizeHandler.d.ts +4 -1
- package/dist/core/ResizePipeline.d.ts +3 -0
- package/dist/core/TextElement.d.ts +13 -0
- package/dist/core/TextMetrics.d.ts +17 -3
- package/dist/core/rich-text-layout.d.ts +2 -0
- package/dist/core/text-spacing.d.ts +74 -0
- package/dist/{custom-icons-CcLMyybv.js → custom-icons-B5TRC1Ed.js} +2 -2
- package/dist/{custom-icons-CcLMyybv.js.map → custom-icons-B5TRC1Ed.js.map} +1 -1
- package/dist/{design-builder-cZY958Rw.js → design-builder-COj0zkxu.js} +2 -2
- package/dist/{design-builder-cZY958Rw.js.map → design-builder-COj0zkxu.js.map} +1 -1
- package/dist/{editorErrors-BhrZ_nwj.js → editorErrors-CCvoKj_P.js} +2 -2
- package/dist/editorErrors-CCvoKj_P.js.map +1 -0
- package/dist/{embed-CJswRZte.js → embed-BTRtoktw.js} +10 -10
- package/dist/{embed-CJswRZte.js.map → embed-BTRtoktw.js.map} +1 -1
- package/dist/embed.mjs +6 -6
- package/dist/errors/editorError.d.ts +7 -1
- package/dist/{guards-DIX8aXWM.js → guards-uQtEQJbs.js} +2 -2
- package/dist/{guards-DIX8aXWM.js.map → guards-uQtEQJbs.js.map} +1 -1
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/useAlignDistribute.d.ts +19 -0
- package/dist/hooks/useTextToolbar.d.ts +8 -0
- package/dist/index.mjs +8 -8
- package/dist/internals.mjs +5 -5
- package/dist/rendering/renderer-types.d.ts +4 -0
- package/dist/rendering/rich-text-renderer.d.ts +4 -0
- package/dist/rendering/text-renderer.d.ts +4 -1
- package/dist/state/CanvasStateV1.d.ts +3 -0
- package/dist/{template-fields-BXl29sgh.js → template-fields-BLRp84Ny.js} +6 -6
- package/dist/{template-fields-BXl29sgh.js.map → template-fields-BLRp84Ny.js.map} +1 -1
- package/dist/testing.mjs +4 -4
- package/dist/types/index.d.ts +26 -0
- package/dist/{usePieceGuideBundle-DE21DQGZ.js → usePieceGuideBundle-CTQszfKA.js} +2 -2
- package/dist/{usePieceGuideBundle-DE21DQGZ.js.map → usePieceGuideBundle-CTQszfKA.js.map} +1 -1
- package/dist/utils/alignDistribute.d.ts +41 -0
- package/dist/utils/featureApplied.d.ts +6 -0
- package/dist/{validation-DGXAQmUC.js → validation-eCebhplp.js} +2991 -2956
- package/dist/validation-eCebhplp.js.map +1 -0
- package/dist/workers/export-worker.bundle.string.d.ts +1 -1
- package/example/package.json +1 -1
- package/package.json +1 -1
- package/dist/CanvasRenderer-yszzrDfo.js.map +0 -1
- package/dist/ContextualToolbars-BHjYsril.js +0 -5556
- package/dist/ContextualToolbars-BHjYsril.js.map +0 -1
- package/dist/ExportManager-BTcyj4cf.js.map +0 -1
- package/dist/FontAnalyzer-EF3gLPr8.js.map +0 -1
- package/dist/UnifiedFontService-CHVYDBDW.js.map +0 -1
- package/dist/editorErrors-BhrZ_nwj.js.map +0 -1
- package/dist/validation-DGXAQmUC.js.map +0 -1
package/dist/types/index.d.ts
CHANGED
|
@@ -323,6 +323,18 @@ export interface CharacterStyle {
|
|
|
323
323
|
underline?: boolean;
|
|
324
324
|
/** Strikethrough decoration */
|
|
325
325
|
strikethrough?: boolean;
|
|
326
|
+
/**
|
|
327
|
+
* Extra advance added after EACH character, in world-space pixels (CSS
|
|
328
|
+
* `letter-spacing` model — the trailing character also gets the spacing, so
|
|
329
|
+
* a line's width grows by `spacing × charCount`). Undefined inherits the
|
|
330
|
+
* element-level `letterSpacing`.
|
|
331
|
+
*
|
|
332
|
+
* Applied by the straight ('custom') text transform only. Curved text
|
|
333
|
+
* transforms (circle, arch, wave, lean, ascend, flag) lay glyphs along
|
|
334
|
+
* their own path math and INTENTIONALLY ignore letter-spacing — see the
|
|
335
|
+
* "curved transforms ignore letterSpacing" test in transform-renderer.test.ts.
|
|
336
|
+
*/
|
|
337
|
+
letterSpacing?: number;
|
|
326
338
|
}
|
|
327
339
|
/**
|
|
328
340
|
* A span of text with uniform styling
|
|
@@ -457,6 +469,20 @@ export interface BaseTextElementConfig extends BaseElementConfig {
|
|
|
457
469
|
italic?: boolean;
|
|
458
470
|
underline?: boolean;
|
|
459
471
|
strikethrough?: boolean;
|
|
472
|
+
/**
|
|
473
|
+
* Element-level default letter-spacing in world-space pixels (extra advance
|
|
474
|
+
* after each character, CSS model). Rich-text spans may override per
|
|
475
|
+
* character via `CharacterStyle.letterSpacing`. Default: 0 (no spacing).
|
|
476
|
+
* Straight ('custom') transform only — curved transforms ignore it.
|
|
477
|
+
*/
|
|
478
|
+
letterSpacing?: number;
|
|
479
|
+
/**
|
|
480
|
+
* Element-level line-height MULTIPLIER for multi-line text
|
|
481
|
+
* (lineSpacing = fontSize × lineHeight). Default: 1.2
|
|
482
|
+
* (LINE_HEIGHT_MULTIPLIER) — the value all existing designs were laid out
|
|
483
|
+
* with, so omitting it never reflows. Straight ('custom') transform only.
|
|
484
|
+
*/
|
|
485
|
+
lineHeight?: number;
|
|
460
486
|
glyphOverrides?: GlyphOverride[];
|
|
461
487
|
openTypeFeatures?: OpenTypeFeatures;
|
|
462
488
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { y as r } from "./FontAnalyzer-
|
|
1
|
+
import { y as r } from "./FontAnalyzer-D4Sg-P6_.js";
|
|
2
2
|
import { useState as a, useEffect as d } from "react";
|
|
3
3
|
const u = "https://api.snowcone.app";
|
|
4
4
|
function h(e) {
|
|
@@ -58,4 +58,4 @@ export {
|
|
|
58
58
|
l as p,
|
|
59
59
|
p as u
|
|
60
60
|
};
|
|
61
|
-
//# sourceMappingURL=usePieceGuideBundle-
|
|
61
|
+
//# sourceMappingURL=usePieceGuideBundle-CTQszfKA.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePieceGuideBundle-
|
|
1
|
+
{"version":3,"file":"usePieceGuideBundle-CTQszfKA.js","sources":["../src/components/embed-realtime/usePieceGuideBundle.ts"],"sourcesContent":["'use client';\n\n/**\n * usePieceGuideBundle — fetch a product's printable-area guides for the embed.\n *\n * Some products hide part of the print file under hardware (a phone case's\n * camera module, a notebook's spine). snowcone.app's own editor draws the\n * printable-area / dead-zone overlay from the product's piece guides;\n * `<RealtimeEmbed>` uses this hook so embedders get the same overlay with\n * zero config — without it, a buyer can edit artwork the mockup will never\n * show, with no signal anywhere (#566).\n *\n * The endpoint is a public read (geometry only — piece rects, safe areas,\n * authored guides; same exposure class as the public catalog). Errors degrade\n * silently to \"no guides\": the editor keeps working, the overlay just doesn't\n * appear — same posture as snowcone.app's own loader.\n *\n * @module\n */\n\nimport { useEffect, useState } from 'react';\nimport type { PieceGuidesConfig } from '../embed/SnowconeCanvas.js';\nimport type {\n PieceGuide,\n PieceGuideRendererPiece,\n PieceGuideRendererStyle,\n} from '../../rendering/PieceGuideRenderer.js';\n\n/** Backend base hosting `GET /printables/by-gvid/:gvid/piece-guides`. */\nexport const PIECE_GUIDES_API_BASE = 'https://api.snowcone.app';\n\n/** Wire shape of the piece-guides endpoint (the consumer-safe subset). */\ninterface RawPieceGuidesResponse {\n artboardWidth: number;\n artboardHeight: number;\n /** Inches→px conversion factor for `safeAreaInsetInches`. */\n templateDpi: number;\n pieces: Array<{\n id: string;\n x: number;\n y: number;\n width: number;\n height: number;\n rotation?: number;\n safeAreaInsetInches?: number;\n }>;\n pieceGuides?: Record<string, PieceGuide>;\n pieceGuidesStyle?: PieceGuideRendererStyle;\n}\n\n/** A fetched bundle: the overlay config plus the artboard frame it was authored in. */\nexport interface PieceGuideBundle {\n /** The print-file frame the piece coords live in. */\n artboardWidth: number;\n artboardHeight: number;\n config: PieceGuidesConfig;\n}\n\n/** Convert the wire response into a renderer-ready bundle (inches → px, etc.). */\nexport function transformPieceGuides(raw: RawPieceGuidesResponse): PieceGuideBundle {\n const pieces: PieceGuideRendererPiece[] = raw.pieces.map((p) => ({\n id: p.id,\n x: p.x,\n y: p.y,\n width: p.width,\n height: p.height,\n rotation: typeof p.rotation === 'number' ? p.rotation : undefined,\n safeAreaInsetPx:\n typeof p.safeAreaInsetInches === 'number' && p.safeAreaInsetInches > 0\n ? p.safeAreaInsetInches * raw.templateDpi\n : undefined,\n }));\n return {\n artboardWidth: raw.artboardWidth,\n artboardHeight: raw.artboardHeight,\n config: {\n pieces,\n guides: raw.pieceGuides ?? {},\n style: raw.pieceGuidesStyle,\n },\n };\n}\n\n/**\n * Gate the bundle on the design's artboard matching the print-file frame the\n * guides were authored in — the same gate snowcone.app's editor applies. A\n * design authored at other dims would render the overlay misaligned, so it's\n * skipped (with a dev hint, since the fix — author at the placement's dims —\n * is on the embedder's side).\n */\nexport function pieceGuidesForArtboard(\n bundle: PieceGuideBundle,\n artboard: { width: number; height: number } | undefined,\n): PieceGuidesConfig | undefined {\n if (!artboard) return undefined;\n if (artboard.width !== bundle.artboardWidth || artboard.height !== bundle.artboardHeight) {\n if (typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {\n console.warn(\n `[RealtimeEmbed] This product has printable-area guides (authored at ` +\n `${bundle.artboardWidth}×${bundle.artboardHeight}), but the design's artboard is ` +\n `${artboard.width}×${artboard.height} — the dead-zone overlay is skipped because the ` +\n `coordinates wouldn't line up. Author the design at the placement's dimensions to get it.`,\n );\n }\n return undefined;\n }\n return bundle.config;\n}\n\n/**\n * Fetch the piece-guide bundle for a variant (gvid). Returns null while\n * loading, when `gvid` is undefined, and on any error (silent degrade).\n */\nexport function usePieceGuideBundle(\n gvid: string | undefined,\n apiBase: string = PIECE_GUIDES_API_BASE,\n): PieceGuideBundle | null {\n const [bundle, setBundle] = useState<PieceGuideBundle | null>(null);\n\n useEffect(() => {\n if (!gvid) {\n setBundle(null);\n return;\n }\n let cancelled = false;\n fetch(`${apiBase}/printables/by-gvid/${encodeURIComponent(gvid)}/piece-guides`)\n .then(async (res) => {\n if (!res.ok) throw new Error(`status ${res.status}`);\n return (await res.json()) as RawPieceGuidesResponse;\n })\n .then((raw) => {\n if (!cancelled) setBundle(transformPieceGuides(raw));\n })\n .catch(() => {\n // No guides is a degraded state, not a failure — the editor keeps\n // working, the overlay just doesn't appear.\n if (!cancelled) setBundle(null);\n });\n return () => {\n cancelled = true;\n };\n }, [gvid, apiBase]);\n\n return bundle;\n}\n"],"names":["PIECE_GUIDES_API_BASE","transformPieceGuides","raw","pieces","p","pieceGuidesForArtboard","bundle","artboard","process","usePieceGuideBundle","gvid","apiBase","setBundle","useState","useEffect","cancelled","res"],"mappings":";;AA6BO,MAAMA,IAAwB;AA8B9B,SAASC,EAAqBC,GAA+C;AAClF,QAAMC,IAAoCD,EAAI,OAAO,IAAI,CAACE,OAAO;AAAA,IAC/D,IAAIA,EAAE;AAAA,IACN,GAAGA,EAAE;AAAA,IACL,GAAGA,EAAE;AAAA,IACL,OAAOA,EAAE;AAAA,IACT,QAAQA,EAAE;AAAA,IACV,UAAU,OAAOA,EAAE,YAAa,WAAWA,EAAE,WAAW;AAAA,IACxD,iBACE,OAAOA,EAAE,uBAAwB,YAAYA,EAAE,sBAAsB,IACjEA,EAAE,sBAAsBF,EAAI,cAC5B;AAAA,EAAA,EACN;AACF,SAAO;AAAA,IACL,eAAeA,EAAI;AAAA,IACnB,gBAAgBA,EAAI;AAAA,IACpB,QAAQ;AAAA,MACN,QAAAC;AAAA,MACA,QAAQD,EAAI,eAAe,CAAA;AAAA,MAC3B,OAAOA,EAAI;AAAA,IAAA;AAAA,EACb;AAEJ;AASO,SAASG,EACdC,GACAC,GAC+B;;AAC/B,MAAKA,GACL;AAAA,QAAIA,EAAS,UAAUD,EAAO,iBAAiBC,EAAS,WAAWD,EAAO,gBAAgB;AACxF,MAAI,OAAOE,IAAY,SAAeA,IAAAA,EAAQ,QAARA,gBAAAA,EAAa,cAAa,gBAC9D,QAAQ;AAAA,QACN,uEACKF,EAAO,aAAa,IAAIA,EAAO,cAAc,mCAC7CC,EAAS,KAAK,IAAIA,EAAS,MAAM;AAAA,MAAA;AAI1C;AAAA,IACF;AACA,WAAOD,EAAO;AAAA;AAChB;AAMO,SAASG,EACdC,GACAC,IAAkBX,GACO;AACzB,QAAM,CAACM,GAAQM,CAAS,IAAIC,EAAkC,IAAI;AAElE,SAAAC,EAAU,MAAM;AACd,QAAI,CAACJ,GAAM;AACT,MAAAE,EAAU,IAAI;AACd;AAAA,IACF;AACA,QAAIG,IAAY;AAChB,iBAAM,GAAGJ,CAAO,uBAAuB,mBAAmBD,CAAI,CAAC,eAAe,EAC3E,KAAK,OAAOM,MAAQ;AACnB,UAAI,CAACA,EAAI,GAAI,OAAM,IAAI,MAAM,UAAUA,EAAI,MAAM,EAAE;AACnD,aAAQ,MAAMA,EAAI,KAAA;AAAA,IACpB,CAAC,EACA,KAAK,CAACd,MAAQ;AACb,MAAKa,KAAWH,EAAUX,EAAqBC,CAAG,CAAC;AAAA,IACrD,CAAC,EACA,MAAM,MAAM;AAGX,MAAKa,KAAWH,EAAU,IAAI;AAAA,IAChC,CAAC,GACI,MAAM;AACX,MAAAG,IAAY;AAAA,IACd;AAAA,EACF,GAAG,CAACL,GAAMC,CAAO,CAAC,GAEXL;AACT;"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { BoundingBox } from '../types/index.js';
|
|
2
|
+
/** Minimum number of movable elements for a distribute operation. */
|
|
3
|
+
export declare const DISTRIBUTE_MIN_COUNT = 3;
|
|
4
|
+
/** Minimum number of elements for aligning relative to the selection itself
|
|
5
|
+
* (below this the caller should align relative to the artboard). */
|
|
6
|
+
export declare const ALIGN_SELECTION_MIN_COUNT = 2;
|
|
7
|
+
export type AlignMode = 'left' | 'center-h' | 'right' | 'top' | 'center-v' | 'bottom';
|
|
8
|
+
export type DistributeMode = 'horizontal' | 'vertical';
|
|
9
|
+
/** Geometry of one alignable element. `x`/`y` is the element's position
|
|
10
|
+
* field (center point for canvas elements); `bbox` its conceptual
|
|
11
|
+
* `getBoundingBox()` rect in the same world coordinates. */
|
|
12
|
+
export interface AlignableItem {
|
|
13
|
+
id: string;
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
bbox: BoundingBox;
|
|
17
|
+
}
|
|
18
|
+
/** New value for an element's position field. */
|
|
19
|
+
export interface PositionUpdate {
|
|
20
|
+
id: string;
|
|
21
|
+
x: number;
|
|
22
|
+
y: number;
|
|
23
|
+
}
|
|
24
|
+
/** Union of the items' bboxes. Returns null for an empty list. */
|
|
25
|
+
export declare function combinedBounds(items: readonly AlignableItem[]): BoundingBox | null;
|
|
26
|
+
/**
|
|
27
|
+
* Align every item's bbox to `frame` (the selection's combined bounds, or the
|
|
28
|
+
* artboard rect for a single element). Returns position updates ONLY for
|
|
29
|
+
* items that actually move, so callers can batch exactly the real changes.
|
|
30
|
+
*/
|
|
31
|
+
export declare function computeAlign(items: readonly AlignableItem[], mode: AlignMode, frame: BoundingBox): PositionUpdate[];
|
|
32
|
+
/**
|
|
33
|
+
* Distribute items along one axis with EQUAL GAPS between bbox edges. The
|
|
34
|
+
* outermost items (first/last along the axis) stay put; everything between
|
|
35
|
+
* is repositioned. Requires at least {@link DISTRIBUTE_MIN_COUNT} items —
|
|
36
|
+
* with fewer there is nothing between the ends to distribute, so calling
|
|
37
|
+
* this is a contradiction and throws (`E_DISTRIBUTE_MIN_THREE`), never a
|
|
38
|
+
* silent no-op. UI callers must not render distribute affordances below the
|
|
39
|
+
* threshold (see `useAlignDistribute().canDistribute`).
|
|
40
|
+
*/
|
|
41
|
+
export declare function computeDistribute(items: readonly AlignableItem[], mode: DistributeMode): PositionUpdate[];
|
|
@@ -10,5 +10,11 @@ export interface FeatureAppliedFlags {
|
|
|
10
10
|
cornerRadius: boolean;
|
|
11
11
|
/** Shape: `transformData.fillOpacity`. */
|
|
12
12
|
shapeTransparency: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Text: non-default `letterSpacing` (≠ 0) or `lineHeight` (≠ 1.2 default).
|
|
15
|
+
* Drives the font-size toolbar button's applied highlight, since both
|
|
16
|
+
* controls live inside the Font Size panel.
|
|
17
|
+
*/
|
|
18
|
+
typography: boolean;
|
|
13
19
|
}
|
|
14
20
|
export declare function getFeatureApplied(element: BaseElement | null | undefined): FeatureAppliedFlags;
|