@snowcone-app/canvas 0.20.0 → 0.21.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/README.md +124 -13
- package/STYLING_CONTRACT.md +427 -0
- package/dist/{EditorCanvasImpl-UBKdG0c0.js → EditorCanvasImpl-2ZgIUVpK.js} +4 -4
- package/dist/{EditorCanvasImpl-UBKdG0c0.js.map → EditorCanvasImpl-2ZgIUVpK.js.map} +1 -1
- package/dist/{SnowconeCanvas-B01o5LmS.js → SnowconeCanvas-DqZicdxK.js} +2 -2
- package/dist/{SnowconeCanvas-B01o5LmS.js.map → SnowconeCanvas-DqZicdxK.js.map} +1 -1
- package/dist/UnifiedFontService-B1PGVu8O.js.map +1 -1
- package/dist/components/editor/EditorAdd.d.ts +2 -1
- package/dist/components/editor/EditorBuy.d.ts +2 -1
- package/dist/components/editor/EditorCanvas.d.ts +2 -1
- package/dist/components/editor/EditorControls.d.ts +2 -1
- package/dist/components/editor/EditorHistory.d.ts +2 -1
- package/dist/components/editor/EditorLayers.d.ts +2 -1
- package/dist/components/editor/EditorMenu.d.ts +2 -1
- package/dist/components/editor/EditorPreview.d.ts +2 -1
- package/dist/components/editor/EditorRoot.d.ts +2 -1
- package/dist/components/editor/EditorTextField.d.ts +2 -1
- package/dist/components/editor/useProductSelection.d.ts +2 -1
- package/dist/components/embed-realtime/context.d.ts +1 -1
- package/dist/contexts/EditorContext.d.ts +1 -1
- package/dist/default-editor/SnowconeEditor.d.ts +2 -1
- package/dist/design/design-builder.d.ts +2 -2
- package/dist/{design-builder-DMKx9UXD.js → design-builder-CPF60RU2.js} +1 -2
- package/dist/design-builder-CPF60RU2.js.map +1 -0
- package/dist/editorErrors-CK1RISFa.js.map +1 -1
- package/dist/{embed-uALcVSYN.js → embed-2tp6Ue90.js} +4 -4
- package/dist/embed-2tp6Ue90.js.map +1 -0
- package/dist/embed.mjs +3 -3
- package/dist/index.mjs +3 -3
- package/dist/{template-fields-B9RagVpT.js → template-fields-FgImixyv.js} +2 -2
- package/dist/{template-fields-B9RagVpT.js.map → template-fields-FgImixyv.js.map} +1 -1
- package/dist/testing/assertRenders.d.ts +2 -1
- package/dist/testing.mjs +1 -1
- package/dist/testing.mjs.map +1 -1
- package/example/README.md +179 -0
- package/example/index.html +12 -0
- package/example/package.json +24 -0
- package/example/src/Widget.tsx +62 -0
- package/example/src/main.tsx +9 -0
- package/example/tsconfig.json +14 -0
- package/example/vite.config.ts +11 -0
- package/llms.txt +45 -0
- package/package.json +6 -2
- package/dist/design-builder-DMKx9UXD.js.map +0 -1
- package/dist/embed-uALcVSYN.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
# @snowcone-app/canvas
|
|
2
2
|
|
|
3
|
-
> **Building with an AI agent?**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
> **Building with an AI agent?** This tarball is self-sufficient: start with
|
|
4
|
+
> [`llms.txt`](./llms.txt) (the agent index), copy the canonical example in
|
|
5
|
+
> [`example/`](./example/README.md), and restyle via the generated
|
|
6
|
+
> [`STYLING_CONTRACT.md`](./STYLING_CONTRACT.md). The complete hosted docs are
|
|
7
|
+
> one fetch — <https://developers.snowcone.app/llms-full.txt> — grep them
|
|
8
|
+
> before assuming a field or behavior is undocumented.
|
|
9
|
+
|
|
10
|
+
**Embeddable product personalization and design editing for e-commerce.**
|
|
11
|
+
|
|
12
|
+
Two surfaces, one package:
|
|
13
|
+
|
|
14
|
+
1. **`Editor.*` composable parts** (`@snowcone-app/canvas/embed`) — live
|
|
15
|
+
server-rendered product previews, layer-bound form fields, and smart
|
|
16
|
+
add-to-cart, composed in your own layout. The turnkey `SnowconeEditor` is
|
|
17
|
+
the same parts in our arrangement.
|
|
18
|
+
2. **`SnowconeCanvas`** (`@snowcone-app/canvas`) — the standalone design
|
|
19
|
+
editor: text, images, transforms, and effects across one or more print
|
|
20
|
+
areas. The same editor that powers Edit and Remix on
|
|
21
|
+
[snowcone.app](https://snowcone.app).
|
|
12
22
|
|
|
13
23
|
📖 **Full documentation: [developers.snowcone.app/canvas](https://developers.snowcone.app/canvas)**
|
|
14
24
|
|
|
@@ -21,7 +31,110 @@ npm install @snowcone-app/canvas
|
|
|
21
31
|
|
|
22
32
|
`react` and `react-dom` (>=18) are peer dependencies.
|
|
23
33
|
|
|
24
|
-
##
|
|
34
|
+
## The canonical example — a personalization widget, no canvas
|
|
35
|
+
|
|
36
|
+
The fastest path to a working integration. A live product preview, one
|
|
37
|
+
personalized text layer, and a completeness-gated add-to-cart — the full
|
|
38
|
+
design editor never loads. This file ships in this package at
|
|
39
|
+
[`example/src/Widget.tsx`](./example/src/Widget.tsx); copy it and change the
|
|
40
|
+
design, the product ids, and the parts.
|
|
41
|
+
|
|
42
|
+
<!-- canonical-example (verbatim from example/src/Widget.tsx; drift-gated) -->
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
import React from 'react';
|
|
46
|
+
import { Editor, design, type EditorBuyPayload } from '@snowcone-app/canvas/embed';
|
|
47
|
+
|
|
48
|
+
const SHOP_KEY = import.meta.env.VITE_SNOWCONE_SHOP ?? 'demo-shop';
|
|
49
|
+
|
|
50
|
+
// The design is the single source of truth: it seeds the first render AND
|
|
51
|
+
// mints the typed layer tokens the TextField binds with.
|
|
52
|
+
const nameDrop = design({ name: 'Front', width: 1480, height: 2328 })
|
|
53
|
+
.background('https://dqMwU8Jct3.storage.snowcone.app/demo/demo-008.jpg', {
|
|
54
|
+
aspectRatio: 1480 / 2328,
|
|
55
|
+
name: 'art',
|
|
56
|
+
})
|
|
57
|
+
.text('YOUR NAME', { anchor: 'top', name: 'name' });
|
|
58
|
+
|
|
59
|
+
const product = {
|
|
60
|
+
productId: 'BEEB77',
|
|
61
|
+
mockupIds: ['FV1qjO'],
|
|
62
|
+
placements: ['Front'],
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export function PersonalizationWidget({
|
|
66
|
+
onAddToCart,
|
|
67
|
+
}: {
|
|
68
|
+
/** Your cart handler. NOTE the payload's `designState` is the EDITOR shape
|
|
69
|
+
* (flat `elements` + artboard metadata) — persist it as-is. It is NOT the
|
|
70
|
+
* server render shape (`artboards[].elements`) the session pushes;
|
|
71
|
+
* `payload.serverRequest` carries that one if you need it. */
|
|
72
|
+
onAddToCart?: (payload: EditorBuyPayload) => void;
|
|
73
|
+
} = {}): React.ReactElement {
|
|
74
|
+
return (
|
|
75
|
+
<Editor.Root shop={SHOP_KEY} product={product} design={nameDrop} grantUrl="/api/realtime/grant">
|
|
76
|
+
<div style={{ display: 'grid', gap: 12, maxWidth: 320 }}>
|
|
77
|
+
<Editor.Preview alt="Your personalized tee" fallback={<div aria-busy="true">Rendering…</div>} />
|
|
78
|
+
<Editor.TextField layer={nameDrop.layer('name')} placeholder="Your name" />
|
|
79
|
+
<Editor.Buy
|
|
80
|
+
onAddToCart={
|
|
81
|
+
onAddToCart ??
|
|
82
|
+
(({ productId, quantity, designState }) => {
|
|
83
|
+
// The host's one line: persist the designState (the
|
|
84
|
+
// re-renderable source of truth) with the cart line.
|
|
85
|
+
console.log('add-to-cart', { productId, quantity, designState });
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
>
|
|
89
|
+
Add to cart · $24
|
|
90
|
+
</Editor.Buy>
|
|
91
|
+
</div>
|
|
92
|
+
</Editor.Root>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Don't forget the stylesheet (once, anywhere in your app):
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
import '@snowcone-app/canvas/style.css';
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`grantUrl` points at a small route on YOUR server that mints a render grant
|
|
104
|
+
(your keys never reach the browser). Framework variants (Next.js, Remix,
|
|
105
|
+
Express) are in the docs: <https://developers.snowcone.app/primitives>.
|
|
106
|
+
|
|
107
|
+
### The parts
|
|
108
|
+
|
|
109
|
+
All from `@snowcone-app/canvas/embed`, all valid in any subset under one
|
|
110
|
+
`Editor.Root` — the host owns the layout:
|
|
111
|
+
|
|
112
|
+
| Part | What it carries |
|
|
113
|
+
|------|-----------------|
|
|
114
|
+
| `Editor.Root` | session, design state, error boundary — the one provider |
|
|
115
|
+
| `Editor.Preview` | live product mockup (×N under one Root is valid) |
|
|
116
|
+
| `Editor.TextField` | input bound to a design text layer by typed token |
|
|
117
|
+
| `Editor.Buy` | completeness gating + cart payload; submit is your callback |
|
|
118
|
+
| `Editor.Canvas` | the editable artboard (the one lazy-loaded heavy chunk) |
|
|
119
|
+
| `Editor.Layers` / `Editor.Controls` / `Editor.History` / `Editor.Menu` / `Editor.Add` | editor chrome as parts |
|
|
120
|
+
|
|
121
|
+
The turnkey default — the same parts in our arrangement:
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
import { SnowconeEditor } from '@snowcone-app/canvas/embed';
|
|
125
|
+
|
|
126
|
+
<SnowconeEditor shop={SHOP_KEY} product={product} design={d} grantUrl="/api/realtime/grant" onAddToCart={addLine} />
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Every part renders minimal DOM tagged `data-sc-part` and themes through
|
|
130
|
+
`.sc-root` CSS custom properties — the full generated reference is
|
|
131
|
+
[`STYLING_CONTRACT.md`](./STYLING_CONTRACT.md). Composition mistakes throw
|
|
132
|
+
structured `EditorError`s (stable `code` + `error.data`) in dev AND prod,
|
|
133
|
+
contained by the Root's error boundary. Test with
|
|
134
|
+
`@snowcone-app/canvas/testing`: `assertRenders` proves a design's bindings
|
|
135
|
+
are alive; `expectNoEditorErrors()` makes contained errors fail your tests.
|
|
136
|
+
|
|
137
|
+
## The standalone design editor
|
|
25
138
|
|
|
26
139
|
```tsx
|
|
27
140
|
import { SnowconeCanvas } from '@snowcone-app/canvas';
|
|
@@ -45,8 +158,6 @@ That renders a working editor out of the box (toolbar, layers, text/image/effect
|
|
|
45
158
|
Use the `kit` prop to switch presets (`'pro-studio'` default, `'compact-customizer'`,
|
|
46
159
|
`'embed-only'`) or pass `layoutConfig` to go canvas-only.
|
|
47
160
|
|
|
48
|
-
## Common recipes
|
|
49
|
-
|
|
50
161
|
### Multiple print areas (placements)
|
|
51
162
|
|
|
52
163
|
```tsx
|
|
@@ -95,7 +206,7 @@ const [state, setState] = useState<CanvasState | null>(null);
|
|
|
95
206
|
|
|
96
207
|
Exports always produce transparent PNGs/WebP — the preview background is never baked in.
|
|
97
208
|
|
|
98
|
-
|
|
209
|
+
### API at a glance
|
|
99
210
|
|
|
100
211
|
`<SnowconeCanvas />` is the single entry point. Key props:
|
|
101
212
|
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
# `Editor.*` styling contract
|
|
2
|
+
|
|
3
|
+
<!-- GENERATED — do not edit. Derived from the parts themselves by
|
|
4
|
+
packages/canvas/src/components/editor/__tests__/stylingContract.test.tsx.
|
|
5
|
+
Regenerate: pnpm -C packages/canvas gen:styling-contract -->
|
|
6
|
+
|
|
7
|
+
Every `Editor.*` part renders minimal styled DOM carrying a stable
|
|
8
|
+
`data-sc-part` attribute, accepts `className`/`style`, and is themed by the
|
|
9
|
+
`.sc-root` CSS custom properties below. Restyle by targeting
|
|
10
|
+
`[data-sc-part="…"]` selectors and overriding `.sc-root` tokens — you never
|
|
11
|
+
need this package's source. Attribute values shown are the stable enums;
|
|
12
|
+
`<angle-bracket>` values are runtime data. `sc-*` classes are additional
|
|
13
|
+
stable hooks; `sc:`-prefixed utility classes are internal and NOT contract.
|
|
14
|
+
|
|
15
|
+
## Part index
|
|
16
|
+
|
|
17
|
+
| `data-sc-part` | rendered by |
|
|
18
|
+
|---|---|
|
|
19
|
+
| `add` | Editor.Add |
|
|
20
|
+
| `buy` | Editor.Buy |
|
|
21
|
+
| `canvas` | Editor.Canvas |
|
|
22
|
+
| `controls` | Editor.Controls |
|
|
23
|
+
| `error` | Editor.Root |
|
|
24
|
+
| `error-retry` | Editor.Root |
|
|
25
|
+
| `history` | Editor.History |
|
|
26
|
+
| `layers` | Editor.Layers |
|
|
27
|
+
| `layers-item` | Editor.Layers |
|
|
28
|
+
| `menu` | Editor.Menu |
|
|
29
|
+
| `preview` | Editor.Preview |
|
|
30
|
+
| `preview-error` | Editor.Preview |
|
|
31
|
+
| `root` | Editor.Root |
|
|
32
|
+
| `snowcone-editor` | SnowconeEditor |
|
|
33
|
+
| `snowcone-editor-header` | SnowconeEditor |
|
|
34
|
+
| `snowcone-editor-idle-dock` | SnowconeEditor |
|
|
35
|
+
| `snowcone-editor-layer-thumb` | SnowconeEditor |
|
|
36
|
+
| `snowcone-editor-menu` | SnowconeEditor |
|
|
37
|
+
| `snowcone-editor-menu-item` | SnowconeEditor |
|
|
38
|
+
| `snowcone-editor-menu-list` | SnowconeEditor |
|
|
39
|
+
| `snowcone-editor-pip` | SnowconeEditor |
|
|
40
|
+
| `snowcone-editor-stage` | SnowconeEditor |
|
|
41
|
+
| `snowcone-editor-swap-back` | SnowconeEditor |
|
|
42
|
+
| `snowcone-editor-swap-stage` | SnowconeEditor |
|
|
43
|
+
| `snowcone-editor-zone` | SnowconeEditor |
|
|
44
|
+
| `textfield` | Editor.TextField |
|
|
45
|
+
|
|
46
|
+
## The DOM each part renders
|
|
47
|
+
|
|
48
|
+
### `add` — Editor.Add
|
|
49
|
+
|
|
50
|
+
```html
|
|
51
|
+
<div data-sc-part="add">
|
|
52
|
+
<div>
|
|
53
|
+
<div>
|
|
54
|
+
<button aria-label=<label>>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### `buy` — Editor.Buy — incomplete (seed value untouched)
|
|
58
|
+
|
|
59
|
+
```html
|
|
60
|
+
<button data-sc-buy-missing=<reason-codes> data-sc-buy-state="incomplete" data-sc-part="buy" disabled type="button">
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### `buy` — Editor.Buy — ready
|
|
64
|
+
|
|
65
|
+
```html
|
|
66
|
+
<button data-sc-buy-state="ready" data-sc-part="buy" type="button">
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### `canvas` — Editor.Canvas
|
|
70
|
+
|
|
71
|
+
```html
|
|
72
|
+
<div class="sc-canvas-frame" data-sc-part="canvas">
|
|
73
|
+
<div class="sc-root">
|
|
74
|
+
<div>
|
|
75
|
+
<div>
|
|
76
|
+
<div role="status">
|
|
77
|
+
<canvas aria-label=<label> role="application">
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### `controls` — Editor.Controls — placement="docked", element selected
|
|
81
|
+
|
|
82
|
+
```html
|
|
83
|
+
<div data-sc-part="controls" data-sc-placement="docked">
|
|
84
|
+
<div>
|
|
85
|
+
<div>
|
|
86
|
+
<div>
|
|
87
|
+
<div class="sc-toolbar" data-mode="embedded">
|
|
88
|
+
…
|
|
89
|
+
<div class="sc-secondary-toolbar-panel-wrapper">
|
|
90
|
+
<div> ×16
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### `controls` — Editor.Controls — built-in floating toolbar (no Controls mounted; body portal)
|
|
94
|
+
|
|
95
|
+
```html
|
|
96
|
+
<div class="sc-toolbar" data-mode="floating" data-sc-part="controls">
|
|
97
|
+
<div class="sc-toolbar-slide-container">
|
|
98
|
+
<div class="sc-toolbar-regular-content">
|
|
99
|
+
<span>
|
|
100
|
+
<span>
|
|
101
|
+
<button aria-label=<label> aria-pressed="false">
|
|
102
|
+
<svg>
|
|
103
|
+
…
|
|
104
|
+
<div class="sc-toolbar-items">
|
|
105
|
+
<button aria-label=<label> title=<title>>
|
|
106
|
+
…
|
|
107
|
+
<button aria-label=<label> aria-pressed="false">
|
|
108
|
+
…
|
|
109
|
+
<button aria-label=<label> class="sc-toolbar-btn">
|
|
110
|
+
…
|
|
111
|
+
<button aria-label=<label> aria-pressed="false">
|
|
112
|
+
… ×5
|
|
113
|
+
<button aria-expanded="false" aria-label=<label> class="sc-toolbar-btn" type="button">
|
|
114
|
+
<button aria-label=<label> aria-pressed="false">
|
|
115
|
+
… ×6
|
|
116
|
+
<button aria-label=<label> aria-pressed="false" disabled>
|
|
117
|
+
…
|
|
118
|
+
<button aria-expanded="false" aria-label=<label> class="sc-toolbar-btn" type="button">
|
|
119
|
+
<div class="sc-toolbar-collapsed-header">
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### `error` — Editor.Root — contained-error card (E_CHUNK_LOAD_FAILED shown)
|
|
123
|
+
|
|
124
|
+
```html
|
|
125
|
+
<div data-sc-part="error" role="alert">
|
|
126
|
+
<p> ×2
|
|
127
|
+
<button data-sc-part="error-retry" type="button">
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### `error-retry` — Editor.Root — the retry affordance (retryable errors only)
|
|
131
|
+
|
|
132
|
+
```html
|
|
133
|
+
<button data-sc-part="error-retry" type="button">
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### `history` — Editor.History
|
|
137
|
+
|
|
138
|
+
```html
|
|
139
|
+
<div data-sc-part="history">
|
|
140
|
+
<div class="sc-undo-redo">
|
|
141
|
+
<button aria-label=<label> aria-pressed="false" disabled>
|
|
142
|
+
<span class="sc-normalized-icon-responsive"> ×2
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### `layers` — Editor.Layers — with a mounted Canvas (live document panel)
|
|
146
|
+
|
|
147
|
+
```html
|
|
148
|
+
<div data-sc-direction="vertical" data-sc-part="layers">
|
|
149
|
+
<div class="sc-layers-panel">
|
|
150
|
+
<div>
|
|
151
|
+
<span>
|
|
152
|
+
<div>
|
|
153
|
+
<div>
|
|
154
|
+
<div> ×2
|
|
155
|
+
<div>
|
|
156
|
+
… ×2
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### `layers` — Editor.Layers — canvas-free (design layers from the store)
|
|
160
|
+
|
|
161
|
+
```html
|
|
162
|
+
<div data-sc-direction="vertical" data-sc-part="layers">
|
|
163
|
+
<div>
|
|
164
|
+
<div data-sc-layer-kind="custom" data-sc-part="layers-item">
|
|
165
|
+
<span>
|
|
166
|
+
<div data-sc-layer-kind="image" data-sc-part="layers-item">
|
|
167
|
+
<span>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### `layers-item` — Editor.Layers — one canvas-free layer row
|
|
171
|
+
|
|
172
|
+
```html
|
|
173
|
+
<div data-sc-layer-kind="custom" data-sc-part="layers-item">
|
|
174
|
+
<span>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### `menu` — Editor.Menu
|
|
178
|
+
|
|
179
|
+
```html
|
|
180
|
+
<div data-sc-part="menu">
|
|
181
|
+
<div>
|
|
182
|
+
<div>
|
|
183
|
+
<button aria-label=<label>>
|
|
184
|
+
<input type="file">
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### `preview` — Editor.Preview — with a rendered mockup
|
|
188
|
+
|
|
189
|
+
```html
|
|
190
|
+
<img alt=<alt> data-sc-part="preview" src=<url>>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### `preview-error` — Editor.Preview — render/grant failure before any mockup
|
|
194
|
+
|
|
195
|
+
```html
|
|
196
|
+
<div data-sc-error=<message> data-sc-part="preview-error" role="alert">
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### `root` — Editor.Root
|
|
200
|
+
|
|
201
|
+
```html
|
|
202
|
+
<div class="sc-root" data-sc-part="root">
|
|
203
|
+
<img alt=<alt> data-sc-part="preview" src=<url>>
|
|
204
|
+
<input aria-label=<label> data-sc-layer=<layer-name> data-sc-part="textfield" placeholder=<placeholder> type="text">
|
|
205
|
+
<button data-sc-buy-missing=<reason-codes> data-sc-buy-state="incomplete" data-sc-part="buy" disabled type="button">
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### `snowcone-editor` — SnowconeEditor
|
|
209
|
+
|
|
210
|
+
```html
|
|
211
|
+
<div data-sc-part="snowcone-editor">
|
|
212
|
+
<div data-sc-part="snowcone-editor-header">
|
|
213
|
+
<span>
|
|
214
|
+
<div data-sc-part="history">
|
|
215
|
+
<div class="sc-undo-redo">
|
|
216
|
+
<button aria-label=<label> aria-pressed="false" disabled>
|
|
217
|
+
… ×2
|
|
218
|
+
<div>
|
|
219
|
+
<button aria-expanded="false" aria-label=<label> data-sc-part="snowcone-editor-menu" type="button">
|
|
220
|
+
<button data-sc-buy-state="ready" data-sc-part="buy" type="button">
|
|
221
|
+
<div data-sc-part="snowcone-editor-stage">
|
|
222
|
+
<div class="sc-canvas-frame" data-sc-part="canvas">
|
|
223
|
+
<div class="sc-root">
|
|
224
|
+
<div>
|
|
225
|
+
…
|
|
226
|
+
<button aria-label=<label> data-sc-part="snowcone-editor-pip" type="button">
|
|
227
|
+
<span aria-busy="true">
|
|
228
|
+
<div data-sc-part="snowcone-editor-zone" data-sc-zone-state="idle">
|
|
229
|
+
<div>
|
|
230
|
+
<div data-sc-part="controls" data-sc-placement="docked">
|
|
231
|
+
<div>
|
|
232
|
+
…
|
|
233
|
+
<div data-sc-part="snowcone-editor-idle-dock">
|
|
234
|
+
<div data-sc-part="add">
|
|
235
|
+
<div>
|
|
236
|
+
…
|
|
237
|
+
<div>
|
|
238
|
+
<button aria-label=<label> data-sc-part="snowcone-editor-layer-thumb" type="button">
|
|
239
|
+
<span> ×2
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### `snowcone-editor-header` — SnowconeEditor — header
|
|
243
|
+
|
|
244
|
+
```html
|
|
245
|
+
<div data-sc-part="snowcone-editor-header">
|
|
246
|
+
<span>
|
|
247
|
+
<div data-sc-part="history">
|
|
248
|
+
<div class="sc-undo-redo">
|
|
249
|
+
<button aria-label=<label> aria-pressed="false" disabled>
|
|
250
|
+
<span class="sc-normalized-icon-responsive"> ×2
|
|
251
|
+
<div>
|
|
252
|
+
<button aria-expanded="false" aria-label=<label> data-sc-part="snowcone-editor-menu" type="button">
|
|
253
|
+
<button data-sc-buy-state="ready" data-sc-part="buy" type="button">
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### `snowcone-editor-idle-dock` — SnowconeEditor — idle dock
|
|
257
|
+
|
|
258
|
+
```html
|
|
259
|
+
<div data-sc-part="snowcone-editor-idle-dock">
|
|
260
|
+
<div data-sc-part="add">
|
|
261
|
+
<div>
|
|
262
|
+
<div>
|
|
263
|
+
<button aria-label=<label>>
|
|
264
|
+
<div>
|
|
265
|
+
<button aria-label=<label> data-sc-part="snowcone-editor-layer-thumb" type="button">
|
|
266
|
+
<span> ×2
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### `snowcone-editor-layer-thumb` — SnowconeEditor — one layer thumbnail
|
|
270
|
+
|
|
271
|
+
```html
|
|
272
|
+
<button aria-label=<label> data-sc-part="snowcone-editor-layer-thumb" type="button">
|
|
273
|
+
<span>
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### `snowcone-editor-menu` — SnowconeEditor — the ⋯ menu button
|
|
277
|
+
|
|
278
|
+
```html
|
|
279
|
+
<button aria-expanded="false" aria-label=<label> data-sc-part="snowcone-editor-menu" type="button">
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### `snowcone-editor-menu-item` — SnowconeEditor — one ⋯ menu item
|
|
283
|
+
|
|
284
|
+
```html
|
|
285
|
+
<button data-sc-part="snowcone-editor-menu-item" role="menuitem" type="button">
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### `snowcone-editor-menu-list` — SnowconeEditor — open ⋯ menu
|
|
289
|
+
|
|
290
|
+
```html
|
|
291
|
+
<div data-sc-part="snowcone-editor-menu-list" role="menu">
|
|
292
|
+
<button data-sc-part="snowcone-editor-menu-item" role="menuitem" type="button"> ×2
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### `snowcone-editor-pip` — SnowconeEditor — preview PiP
|
|
296
|
+
|
|
297
|
+
```html
|
|
298
|
+
<button aria-label=<label> data-sc-part="snowcone-editor-pip" type="button">
|
|
299
|
+
<span aria-busy="true">
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### `snowcone-editor-stage` — SnowconeEditor — stage
|
|
303
|
+
|
|
304
|
+
```html
|
|
305
|
+
<div data-sc-part="snowcone-editor-stage">
|
|
306
|
+
<div class="sc-canvas-frame" data-sc-part="canvas">
|
|
307
|
+
<div class="sc-root">
|
|
308
|
+
<div>
|
|
309
|
+
<div>
|
|
310
|
+
…
|
|
311
|
+
<button aria-label=<label> data-sc-part="snowcone-editor-pip" type="button">
|
|
312
|
+
<span aria-busy="true">
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### `snowcone-editor-swap-back` — SnowconeEditor — the "artwork" chip back to the canvas
|
|
316
|
+
|
|
317
|
+
```html
|
|
318
|
+
<button aria-label=<label> data-sc-part="snowcone-editor-swap-back" type="button">
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### `snowcone-editor-swap-stage` — SnowconeEditor — mockup swapped onto the stage
|
|
322
|
+
|
|
323
|
+
```html
|
|
324
|
+
<div data-sc-part="snowcone-editor-swap-stage">
|
|
325
|
+
<div aria-busy="true">
|
|
326
|
+
<button aria-label=<label> data-sc-part="snowcone-editor-swap-back" type="button">
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### `snowcone-editor-zone` — SnowconeEditor — morphing control zone (idle)
|
|
330
|
+
|
|
331
|
+
```html
|
|
332
|
+
<div data-sc-part="snowcone-editor-zone" data-sc-zone-state="idle">
|
|
333
|
+
<div>
|
|
334
|
+
<div data-sc-part="controls" data-sc-placement="docked">
|
|
335
|
+
<div>
|
|
336
|
+
<div>
|
|
337
|
+
<div>
|
|
338
|
+
…
|
|
339
|
+
<div class="sc-secondary-toolbar-panel-wrapper">
|
|
340
|
+
… ×16
|
|
341
|
+
<div data-sc-part="snowcone-editor-idle-dock">
|
|
342
|
+
<div data-sc-part="add">
|
|
343
|
+
<div>
|
|
344
|
+
<div>
|
|
345
|
+
…
|
|
346
|
+
<div>
|
|
347
|
+
<button aria-label=<label> data-sc-part="snowcone-editor-layer-thumb" type="button">
|
|
348
|
+
<span> ×2
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### `textfield` — Editor.TextField
|
|
352
|
+
|
|
353
|
+
```html
|
|
354
|
+
<input aria-label=<label> data-sc-layer=<layer-name> data-sc-part="textfield" placeholder=<placeholder> type="text">
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## `.sc-root` theming tokens
|
|
358
|
+
|
|
359
|
+
Declared on `.sc-root` (applied once by `Editor.Root`); override any of
|
|
360
|
+
them on your own wrapper around the Root to retheme every part. Portaled
|
|
361
|
+
UI (the floating toolbar) carries its own `.sc-root` wrapper, so overrides
|
|
362
|
+
on `:root` or above the widget apply there too.
|
|
363
|
+
|
|
364
|
+
| group | token | default |
|
|
365
|
+
|---|---|---|
|
|
366
|
+
| Semantic Colors | `--color-primary` | `#000000` |
|
|
367
|
+
| Semantic Colors | `--color-primary-foreground` | `#ffffff` |
|
|
368
|
+
| Semantic Colors | `--color-secondary` | `#f4f4f5` |
|
|
369
|
+
| Semantic Colors | `--color-secondary-foreground` | `#18181b` |
|
|
370
|
+
| Semantic Colors | `--color-default` | `#f0f0f1` |
|
|
371
|
+
| Semantic Colors | `--color-default-foreground` | `#18181b` |
|
|
372
|
+
| Semantic Colors | `--color-danger` | `#ef4444` |
|
|
373
|
+
| Semantic Colors | `--color-danger-foreground` | `#ffffff` |
|
|
374
|
+
| Semantic Colors | `--color-success` | `#22c55e` |
|
|
375
|
+
| Semantic Colors | `--color-success-foreground` | `#ffffff` |
|
|
376
|
+
| Semantic Colors | `--color-warning` | `#f59e0b` |
|
|
377
|
+
| Semantic Colors | `--color-warning-foreground` | `#000000` |
|
|
378
|
+
| Layout Colors | `--color-background` | `#f8f8f8` |
|
|
379
|
+
| Layout Colors | `--color-foreground` | `#0a0a0a` |
|
|
380
|
+
| Layout Colors | `--color-surface` | `#ffffff` |
|
|
381
|
+
| Layout Colors | `--color-surface-foreground` | `#0a0a0a` |
|
|
382
|
+
| Layout Colors | `--color-muted` | `#f4f4f5` |
|
|
383
|
+
| Layout Colors | `--color-muted-foreground` | `#71717a` |
|
|
384
|
+
| Layout Colors | `--color-divider` | `#e4e4e7` |
|
|
385
|
+
| Layout Colors | `--color-focus` | `#000000` |
|
|
386
|
+
| Field/Input Colors | `--color-field` | `#ffffff` |
|
|
387
|
+
| Field/Input Colors | `--color-field-foreground` | `#0a0a0a` |
|
|
388
|
+
| Field/Input Colors | `--color-field-placeholder` | `#71717a` |
|
|
389
|
+
| Field/Input Colors | `--color-field-border` | `transparent` |
|
|
390
|
+
| Accent (backward compat) | `--color-accent` | `#000000` |
|
|
391
|
+
| Accent (backward compat) | `--color-accent-foreground` | `#ffffff` |
|
|
392
|
+
| Shadcn compatibility aliases | `--color-card` | `#ffffff` |
|
|
393
|
+
| Shadcn compatibility aliases | `--color-card-foreground` | `#0a0a0a` |
|
|
394
|
+
| Shadcn compatibility aliases | `--color-popover` | `#ffffff` |
|
|
395
|
+
| Shadcn compatibility aliases | `--color-popover-foreground` | `#0a0a0a` |
|
|
396
|
+
| Shadcn compatibility aliases | `--color-border` | `#e4e4e7` |
|
|
397
|
+
| Shadcn compatibility aliases | `--color-destructive` | `#ef4444` |
|
|
398
|
+
| Shadcn compatibility aliases | `--color-destructive-foreground` | `#ffffff` |
|
|
399
|
+
| Shadcn compatibility aliases | `--color-ring` | `#000000` |
|
|
400
|
+
| Shadcn compatibility aliases | `--color-input` | `transparent` |
|
|
401
|
+
| Overlay/Surface | `--color-overlay` | `#ffffff` |
|
|
402
|
+
| Overlay/Surface | `--color-overlay-foreground` | `#0a0a0a` |
|
|
403
|
+
| Overlay/Surface | `--shadow-overlay` | `0 8px 30px rgb(0 0 0 / 0.12)` |
|
|
404
|
+
| Overlay/Surface | `--shadow-surface` | `0 1px 3px rgb(0 0 0 / 0.1)` |
|
|
405
|
+
| Overlay/Surface | `--shadow-soft` | `0 1px 2px rgb(0 0 0 / 0.06), 0 2px 4px rgb(0 0 0 / 0.04)` |
|
|
406
|
+
| Semantic Font Families | `--font-family-heading` | `ui-sans-serif, system-ui, sans-serif` |
|
|
407
|
+
| Semantic Font Families | `--font-family-body` | `ui-sans-serif, system-ui, sans-serif` |
|
|
408
|
+
| Semantic Font Families | `--font-family-label` | `ui-sans-serif, system-ui, sans-serif` |
|
|
409
|
+
| Semantic Font Families | `--font-family-button` | `ui-sans-serif, system-ui, sans-serif` |
|
|
410
|
+
| Semantic Font Families | `--font-family-caption` | `ui-sans-serif, system-ui, sans-serif` |
|
|
411
|
+
| Semantic Font Families | `--font-family-display` | `ui-sans-serif, system-ui, sans-serif` |
|
|
412
|
+
| Semantic Font Families | `--font-family-accent` | `ui-sans-serif, system-ui, sans-serif` |
|
|
413
|
+
| Semantic Radius | `--radius-button` | `4px` |
|
|
414
|
+
| Semantic Radius | `--radius-input` | `4px` |
|
|
415
|
+
| Semantic Radius | `--radius-card` | `6px` |
|
|
416
|
+
| Semantic Radius | `--radius-modal` | `8px` |
|
|
417
|
+
| Semantic Radius | `--radius-badge` | `9999px` |
|
|
418
|
+
| Semantic Radius | `--radius-avatar` | `9999px` |
|
|
419
|
+
| Semantic Radius | `--radius-tooltip` | `6px` |
|
|
420
|
+
| Semantic Radius | `--radius-image` | `4px` |
|
|
421
|
+
| Z-index scale | `--z-base` | `1` |
|
|
422
|
+
| Z-index scale | `--z-sticky` | `1000` |
|
|
423
|
+
| Z-index scale | `--z-dropdown` | `1001` |
|
|
424
|
+
| Z-index scale | `--z-drawer` | `10002` |
|
|
425
|
+
| Z-index scale | `--z-overlay` | `10001` |
|
|
426
|
+
| Z-index scale | `--z-modal` | `10000` |
|
|
427
|
+
| Z-index scale | `--z-tooltip` | `2147483647` |
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx as m, jsxs as R, Fragment as $ } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef as j, useContext as E, useSyncExternalStore as D, useMemo as G, useRef as y, useLayoutEffect as I } from "react";
|
|
3
|
-
import { S as N } from "./SnowconeCanvas-
|
|
3
|
+
import { S as N } from "./SnowconeCanvas-DqZicdxK.js";
|
|
4
4
|
import { d as U } from "./ContextualToolbars--vTtorNg.js";
|
|
5
|
-
import { s as V } from "./design-builder-
|
|
6
|
-
import { u as k } from "./embed-
|
|
5
|
+
import { s as V } from "./design-builder-CPF60RU2.js";
|
|
6
|
+
import { u as k } from "./embed-2tp6Ue90.js";
|
|
7
7
|
import { c as z, b as L } from "./editorErrors-CK1RISFa.js";
|
|
8
8
|
import { T as O } from "./FontAnalyzer-BpsTYowD.js";
|
|
9
9
|
import { p as h } from "./UnifiedFontService-B1PGVu8O.js";
|
|
@@ -79,4 +79,4 @@ const te = j(
|
|
|
79
79
|
export {
|
|
80
80
|
te as EditorCanvasImpl
|
|
81
81
|
};
|
|
82
|
-
//# sourceMappingURL=EditorCanvasImpl-
|
|
82
|
+
//# sourceMappingURL=EditorCanvasImpl-2ZgIUVpK.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditorCanvasImpl-UBKdG0c0.js","sources":["../src/components/editor/EditorCanvasImpl.tsx"],"sourcesContent":["'use client';\n\n/**\n * The heavy half of `Editor.Canvas` (and of the legacy `<RealtimeCanvas>`),\n * extracted from RealtimeCanvas's body. This module statically imports\n * `SnowconeCanvas` — i.e. the full rendering engine — and is therefore ONLY\n * ever reached through a dynamic `import()` (D9): the embed entry's static\n * import graph stays renderer-free, mechanically enforced by the\n * import-graph CI check.\n *\n * Beyond the original wiring (seed → SnowconeCanvas, edit →\n * `serializeStateForServer` → `pushRender`), this adds the ADR-0085 §3 store\n * wiring: every canvas change lands in the Root's {@link DesignStateStore}\n * (the ONE live design state), and a {@link CanvasBridge} is registered so\n * `Editor.TextField` writes route through the editor command path (undo/redo,\n * repaint) while a canvas is mounted — the editor document stays the single\n * write authority.\n */\n\nimport React, {\n forwardRef,\n useContext,\n useLayoutEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n type CSSProperties,\n type ReactNode,\n} from 'react';\nimport { SnowconeCanvas } from '../embed/SnowconeCanvas.js';\nimport { ContextualToolbars } from '../ContextualToolbars.js';\nimport type { CanvasState, SnowconeCanvasHandle } from '../embed/SnowconeCanvas.js';\nimport { serializeStateForServer } from '../../rendering/serialize-for-server.js';\nimport type { KitPresetId, KitDefinition } from '../../kits/types.js';\nimport type { CanvasServices } from '../../contexts/CanvasServicesContext.js';\nimport { useEmbedContext } from '../embed-realtime/context.js';\nimport { useElementsContext } from '../../contexts/ElementsContext.js';\nimport { useCommandContext } from '../../contexts/CommandContext.js';\nimport { TextElement } from '../../core/TextElement.js';\nimport { EditorRootInternalsContext } from './RootInternals.js';\n\nexport interface EditorCanvasImplProps {\n /** Your controls (toolbar, inputs). Rendered as an overlay inside the editor\n * context so binding hooks work. */\n children?: ReactNode;\n /** Editor chrome preset. `Editor.Canvas` pins the chrome-less `'embed-only'`;\n * the legacy `<RealtimeCanvas kit>` prop passes richer presets through. */\n kit?: KitPresetId | KitDefinition;\n /** Inherit the host app's theme instead of managing its own (default true). */\n inheritTheme?: boolean;\n /** Cap the canvas height in px. */\n maxHeight?: number;\n /** Privileged-service wiring override (usually inherited from Root). */\n services?: CanvasServices;\n /** Observe the live design state (the same {@link CanvasState} the Root's\n * state store tracks). Safe to pass inline — read through a ref. */\n onChange?: (state: CanvasState) => void;\n /** Whether the wrapping div self-applies `.sc-root`. The legacy\n * `<RealtimeCanvas>` always did (it could be the only Snowcone DOM on the\n * page); under `Editor.Root` the Root applies it once and parts never\n * self-apply (ADR-0085). Re-applying is harmless (same variables), so the\n * legacy wrapper keeps `true` for bit-identical back-compat. */\n selfScope?: boolean;\n /** `data-sc-part` value for the wrapping div (the parts contract). */\n partAttr?: string;\n /** Mount the built-in floating contextual toolbar (ADR-0085 §5: with no\n * `Editor.Controls` mounted, `Editor.Canvas` defaults to the floating\n * toolbar; a registered DOCKED Controls suppresses it). Set by\n * `Editor.Canvas` only — the legacy `<RealtimeCanvas>` wrapper keeps\n * today's chrome-less behavior. @internal */\n builtinControls?: boolean;\n className?: string;\n style?: CSSProperties;\n}\n\nconst noopSubscribe = (): (() => void) => () => {};\n\n/**\n * Runs INSIDE the canvas's editor context (the overlay slot) and registers\n * the imperative bridge sibling parts use to edit by layer name through the\n * command path. Generalizes `useTextBinding`'s write mechanics to a dynamic\n * name (a hook can't take names at call time).\n */\nfunction CanvasBindingBridge(): null {\n const { elementStore } = useElementsContext();\n const { executeElementUpdate } = useCommandContext();\n // Tolerant on purpose: the legacy <RealtimeCanvas> is supported inside a\n // bare EmbedContext (advanced consumers provide their own); only under an\n // <Editor.Root> is there a registry to bridge into.\n const internals = useContext(EditorRootInternalsContext);\n\n const latest = useRef({ elementStore, executeElementUpdate });\n latest.current = { elementStore, executeElementUpdate };\n\n const registry = internals?.registry ?? null;\n useLayoutEffect(() => {\n if (!registry) return;\n return registry.registerCanvasBridge({\n setTextByName(name: string, text: string): boolean {\n const { elementStore: store, executeElementUpdate: execute } = latest.current;\n const matches = store.getAllByName(name).filter((el): el is TextElement => el instanceof TextElement);\n if (matches.length === 0) return false;\n for (const el of matches) {\n const cloned = el.clone() as TextElement;\n cloned.setText(text);\n execute(el, cloned);\n }\n return true;\n },\n });\n }, [registry]);\n\n return null;\n}\n\nexport const EditorCanvasImpl = forwardRef<SnowconeCanvasHandle, EditorCanvasImplProps>(\n function EditorCanvasImpl(\n {\n children,\n kit = 'embed-only',\n inheritTheme = true,\n maxHeight,\n services,\n onChange,\n selfScope = false,\n partAttr,\n builtinControls = false,\n className,\n style,\n }: EditorCanvasImplProps,\n ref,\n ): React.ReactElement {\n const { placement, throttleMs, pieceGuides, pushRender, stateStore } = useEmbedContext();\n\n // Under an <Editor.Root> the editor document is mounted by the ROOT (P1\n // provider inversion, D9: state always) — SnowconeCanvas must write into\n // that document instead of mounting its own, and a REMOUNT of this part\n // must rebuild the (surviving) document from the live seed instead of\n // accreting onto the previous mount's artboards/elements. The legacy\n // <RealtimeCanvas> inside a bare EmbedContext (no Root) keeps mounting\n // its own provider, exactly as before.\n const rootInternals = useContext(EditorRootInternalsContext);\n const underRoot = rootInternals !== null;\n\n // ADR-0085 §5 (Controls placement resolution): with no Editor.Controls\n // mounted — or a `placement=\"floating\"` one — the canvas owns the\n // floating contextual toolbar; a registered DOCKED Controls suppresses\n // it. Subscribed to the part registry so the zones swap live when a\n // docked Controls mounts/unmounts.\n const partsRegistry = rootInternals?.registry ?? null;\n useSyncExternalStore(\n partsRegistry ? partsRegistry.subscribe : noopSubscribe,\n partsRegistry ? partsRegistry.getVersion : () => 0,\n partsRegistry ? partsRegistry.getVersion : () => 0,\n );\n const showBuiltinFloatingControls =\n builtinControls && underRoot && !(partsRegistry?.hasDockedControls() ?? false);\n\n // Seed from the LIVE store, not the original design (ADR-0085 §3): the\n // store starts as design.toJSON(), so a fresh mount is identical to the\n // old behavior — but edits made while this chunk was loading (e.g. a\n // TextField keystroke in the first second) survive instead of being\n // clobbered by a stale seed. Captured once per store identity; later\n // store changes flow through the editor document, not re-seeding.\n const seed = useMemo(() => stateStore.getState(), [stateStore]);\n\n // Latest-ref so a host's inline `onChange` identity never matters here\n // (SnowconeCanvas already refs ITS callback; this keeps ours symmetric).\n const onChangeRef = useRef(onChange);\n onChangeRef.current = onChange;\n\n const rootClass = selfScope ? 'sc-root' : 'sc-canvas-frame';\n\n return (\n <div className={`${rootClass}${className ? ` ${className}` : ''}`} style={style} data-sc-part={partAttr}>\n <SnowconeCanvas\n ref={ref}\n kit={kit}\n inheritTheme={inheritTheme}\n services={services}\n externalProvider={underRoot}\n resetDocumentOnMount={underRoot}\n artboards={seed.artboards}\n activeArtboard={placement}\n initialElements={seed.elements}\n layoutConfig={maxHeight ? { maxHeight } : undefined}\n overlay={\n <>\n <CanvasBindingBridge />\n {showBuiltinFloatingControls ? <ContextualToolbars mode=\"floating\" partAttr=\"controls\" /> : null}\n {children}\n </>\n }\n // #566: the product's printable-area / dead-zone overlay, fetched and\n // dims-gated by the Root — same overlay snowcone.app's editor\n // shows, so a buyer can't unknowingly edit under a camera cutout.\n pieceGuides={pieceGuides ?? undefined}\n onChange={(state) => {\n // ONE store, last-write-wins (ADR-0085 §3): the editor document is\n // the write authority while mounted; the Root store derives from\n // its onChange, then the render follows the store.\n stateStore.setState(state);\n pushRender(serializeStateForServer(state), throttleMs);\n onChangeRef.current?.(state);\n }}\n />\n </div>\n );\n },\n);\n\nexport type { CanvasState, SnowconeCanvasHandle };\n"],"names":["noopSubscribe","CanvasBindingBridge","elementStore","useElementsContext","executeElementUpdate","useCommandContext","internals","useContext","EditorRootInternalsContext","latest","useRef","registry","useLayoutEffect","name","text","store","execute","matches","el","TextElement","cloned","EditorCanvasImpl","forwardRef","children","kit","inheritTheme","maxHeight","services","onChange","selfScope","partAttr","builtinControls","className","style","ref","placement","throttleMs","pieceGuides","pushRender","stateStore","useEmbedContext","rootInternals","underRoot","partsRegistry","useSyncExternalStore","showBuiltinFloatingControls","seed","useMemo","onChangeRef","jsx","SnowconeCanvas","jsxs","Fragment","ContextualToolbars","state","serializeStateForServer","_a"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EditorCanvasImpl-2ZgIUVpK.js","sources":["../src/components/editor/EditorCanvasImpl.tsx"],"sourcesContent":["'use client';\n\n/**\n * The heavy half of `Editor.Canvas` (and of the legacy `<RealtimeCanvas>`),\n * extracted from RealtimeCanvas's body. This module statically imports\n * `SnowconeCanvas` — i.e. the full rendering engine — and is therefore ONLY\n * ever reached through a dynamic `import()` (D9): the embed entry's static\n * import graph stays renderer-free, mechanically enforced by the\n * import-graph CI check.\n *\n * Beyond the original wiring (seed → SnowconeCanvas, edit →\n * `serializeStateForServer` → `pushRender`), this adds the ADR-0085 §3 store\n * wiring: every canvas change lands in the Root's {@link DesignStateStore}\n * (the ONE live design state), and a {@link CanvasBridge} is registered so\n * `Editor.TextField` writes route through the editor command path (undo/redo,\n * repaint) while a canvas is mounted — the editor document stays the single\n * write authority.\n */\n\nimport React, {\n forwardRef,\n useContext,\n useLayoutEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n type CSSProperties,\n type ReactNode,\n} from 'react';\nimport { SnowconeCanvas } from '../embed/SnowconeCanvas.js';\nimport { ContextualToolbars } from '../ContextualToolbars.js';\nimport type { CanvasState, SnowconeCanvasHandle } from '../embed/SnowconeCanvas.js';\nimport { serializeStateForServer } from '../../rendering/serialize-for-server.js';\nimport type { KitPresetId, KitDefinition } from '../../kits/types.js';\nimport type { CanvasServices } from '../../contexts/CanvasServicesContext.js';\nimport { useEmbedContext } from '../embed-realtime/context.js';\nimport { useElementsContext } from '../../contexts/ElementsContext.js';\nimport { useCommandContext } from '../../contexts/CommandContext.js';\nimport { TextElement } from '../../core/TextElement.js';\nimport { EditorRootInternalsContext } from './RootInternals.js';\n\nexport interface EditorCanvasImplProps {\n /** Your controls (toolbar, inputs). Rendered as an overlay inside the editor\n * context so binding hooks work. */\n children?: ReactNode;\n /** Editor chrome preset. `Editor.Canvas` pins the chrome-less `'embed-only'`;\n * the legacy `<RealtimeCanvas kit>` prop passes richer presets through. */\n kit?: KitPresetId | KitDefinition;\n /** Inherit the host app's theme instead of managing its own (default true). */\n inheritTheme?: boolean;\n /** Cap the canvas height in px. */\n maxHeight?: number;\n /** Privileged-service wiring override (usually inherited from Root). */\n services?: CanvasServices;\n /** Observe the live design state (the same {@link CanvasState} the Root's\n * state store tracks). Safe to pass inline — read through a ref. */\n onChange?: (state: CanvasState) => void;\n /** Whether the wrapping div self-applies `.sc-root`. The legacy\n * `<RealtimeCanvas>` always did (it could be the only Snowcone DOM on the\n * page); under `Editor.Root` the Root applies it once and parts never\n * self-apply (ADR-0085). Re-applying is harmless (same variables), so the\n * legacy wrapper keeps `true` for bit-identical back-compat. */\n selfScope?: boolean;\n /** `data-sc-part` value for the wrapping div (the parts contract). */\n partAttr?: string;\n /** Mount the built-in floating contextual toolbar (ADR-0085 §5: with no\n * `Editor.Controls` mounted, `Editor.Canvas` defaults to the floating\n * toolbar; a registered DOCKED Controls suppresses it). Set by\n * `Editor.Canvas` only — the legacy `<RealtimeCanvas>` wrapper keeps\n * today's chrome-less behavior. @internal */\n builtinControls?: boolean;\n className?: string;\n style?: CSSProperties;\n}\n\nconst noopSubscribe = (): (() => void) => () => {};\n\n/**\n * Runs INSIDE the canvas's editor context (the overlay slot) and registers\n * the imperative bridge sibling parts use to edit by layer name through the\n * command path. Generalizes `useTextBinding`'s write mechanics to a dynamic\n * name (a hook can't take names at call time).\n */\nfunction CanvasBindingBridge(): null {\n const { elementStore } = useElementsContext();\n const { executeElementUpdate } = useCommandContext();\n // Tolerant on purpose: the legacy <RealtimeCanvas> is supported inside a\n // bare EmbedContext (advanced consumers provide their own); only under an\n // <Editor.Root> is there a registry to bridge into.\n const internals = useContext(EditorRootInternalsContext);\n\n const latest = useRef({ elementStore, executeElementUpdate });\n latest.current = { elementStore, executeElementUpdate };\n\n const registry = internals?.registry ?? null;\n useLayoutEffect(() => {\n if (!registry) return;\n return registry.registerCanvasBridge({\n setTextByName(name: string, text: string): boolean {\n const { elementStore: store, executeElementUpdate: execute } = latest.current;\n const matches = store.getAllByName(name).filter((el): el is TextElement => el instanceof TextElement);\n if (matches.length === 0) return false;\n for (const el of matches) {\n const cloned = el.clone() as TextElement;\n cloned.setText(text);\n execute(el, cloned);\n }\n return true;\n },\n });\n }, [registry]);\n\n return null;\n}\n\nexport const EditorCanvasImpl = forwardRef<SnowconeCanvasHandle, EditorCanvasImplProps>(\n function EditorCanvasImpl(\n {\n children,\n kit = 'embed-only',\n inheritTheme = true,\n maxHeight,\n services,\n onChange,\n selfScope = false,\n partAttr,\n builtinControls = false,\n className,\n style,\n }: EditorCanvasImplProps,\n ref,\n ): React.ReactElement {\n const { placement, throttleMs, pieceGuides, pushRender, stateStore } = useEmbedContext();\n\n // Under an <Editor.Root> the editor document is mounted by the ROOT (P1\n // provider inversion, D9: state always) — SnowconeCanvas must write into\n // that document instead of mounting its own, and a REMOUNT of this part\n // must rebuild the (surviving) document from the live seed instead of\n // accreting onto the previous mount's artboards/elements. The legacy\n // <RealtimeCanvas> inside a bare EmbedContext (no Root) keeps mounting\n // its own provider, exactly as before.\n const rootInternals = useContext(EditorRootInternalsContext);\n const underRoot = rootInternals !== null;\n\n // ADR-0085 §5 (Controls placement resolution): with no Editor.Controls\n // mounted — or a `placement=\"floating\"` one — the canvas owns the\n // floating contextual toolbar; a registered DOCKED Controls suppresses\n // it. Subscribed to the part registry so the zones swap live when a\n // docked Controls mounts/unmounts.\n const partsRegistry = rootInternals?.registry ?? null;\n useSyncExternalStore(\n partsRegistry ? partsRegistry.subscribe : noopSubscribe,\n partsRegistry ? partsRegistry.getVersion : () => 0,\n partsRegistry ? partsRegistry.getVersion : () => 0,\n );\n const showBuiltinFloatingControls =\n builtinControls && underRoot && !(partsRegistry?.hasDockedControls() ?? false);\n\n // Seed from the LIVE store, not the original design (ADR-0085 §3): the\n // store starts as design.toJSON(), so a fresh mount is identical to the\n // old behavior — but edits made while this chunk was loading (e.g. a\n // TextField keystroke in the first second) survive instead of being\n // clobbered by a stale seed. Captured once per store identity; later\n // store changes flow through the editor document, not re-seeding.\n const seed = useMemo(() => stateStore.getState(), [stateStore]);\n\n // Latest-ref so a host's inline `onChange` identity never matters here\n // (SnowconeCanvas already refs ITS callback; this keeps ours symmetric).\n const onChangeRef = useRef(onChange);\n onChangeRef.current = onChange;\n\n const rootClass = selfScope ? 'sc-root' : 'sc-canvas-frame';\n\n return (\n <div className={`${rootClass}${className ? ` ${className}` : ''}`} style={style} data-sc-part={partAttr}>\n <SnowconeCanvas\n ref={ref}\n kit={kit}\n inheritTheme={inheritTheme}\n services={services}\n externalProvider={underRoot}\n resetDocumentOnMount={underRoot}\n artboards={seed.artboards}\n activeArtboard={placement}\n initialElements={seed.elements}\n layoutConfig={maxHeight ? { maxHeight } : undefined}\n overlay={\n <>\n <CanvasBindingBridge />\n {showBuiltinFloatingControls ? <ContextualToolbars mode=\"floating\" partAttr=\"controls\" /> : null}\n {children}\n </>\n }\n // #566: the product's printable-area / dead-zone overlay, fetched and\n // dims-gated by the Root — same overlay snowcone.app's editor\n // shows, so a buyer can't unknowingly edit under a camera cutout.\n pieceGuides={pieceGuides ?? undefined}\n onChange={(state) => {\n // ONE store, last-write-wins (ADR-0085 §3): the editor document is\n // the write authority while mounted; the Root store derives from\n // its onChange, then the render follows the store.\n stateStore.setState(state);\n pushRender(serializeStateForServer(state), throttleMs);\n onChangeRef.current?.(state);\n }}\n />\n </div>\n );\n },\n);\n\nexport type { CanvasState, SnowconeCanvasHandle };\n"],"names":["noopSubscribe","CanvasBindingBridge","elementStore","useElementsContext","executeElementUpdate","useCommandContext","internals","useContext","EditorRootInternalsContext","latest","useRef","registry","useLayoutEffect","name","text","store","execute","matches","el","TextElement","cloned","EditorCanvasImpl","forwardRef","children","kit","inheritTheme","maxHeight","services","onChange","selfScope","partAttr","builtinControls","className","style","ref","placement","throttleMs","pieceGuides","pushRender","stateStore","useEmbedContext","rootInternals","underRoot","partsRegistry","useSyncExternalStore","showBuiltinFloatingControls","seed","useMemo","onChangeRef","jsx","SnowconeCanvas","jsxs","Fragment","ContextualToolbars","state","serializeStateForServer","_a"],"mappings":"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"}
|