@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.
Files changed (45) hide show
  1. package/README.md +124 -13
  2. package/STYLING_CONTRACT.md +427 -0
  3. package/dist/{EditorCanvasImpl-UBKdG0c0.js → EditorCanvasImpl-2ZgIUVpK.js} +4 -4
  4. package/dist/{EditorCanvasImpl-UBKdG0c0.js.map → EditorCanvasImpl-2ZgIUVpK.js.map} +1 -1
  5. package/dist/{SnowconeCanvas-B01o5LmS.js → SnowconeCanvas-DqZicdxK.js} +2 -2
  6. package/dist/{SnowconeCanvas-B01o5LmS.js.map → SnowconeCanvas-DqZicdxK.js.map} +1 -1
  7. package/dist/UnifiedFontService-B1PGVu8O.js.map +1 -1
  8. package/dist/components/editor/EditorAdd.d.ts +2 -1
  9. package/dist/components/editor/EditorBuy.d.ts +2 -1
  10. package/dist/components/editor/EditorCanvas.d.ts +2 -1
  11. package/dist/components/editor/EditorControls.d.ts +2 -1
  12. package/dist/components/editor/EditorHistory.d.ts +2 -1
  13. package/dist/components/editor/EditorLayers.d.ts +2 -1
  14. package/dist/components/editor/EditorMenu.d.ts +2 -1
  15. package/dist/components/editor/EditorPreview.d.ts +2 -1
  16. package/dist/components/editor/EditorRoot.d.ts +2 -1
  17. package/dist/components/editor/EditorTextField.d.ts +2 -1
  18. package/dist/components/editor/useProductSelection.d.ts +2 -1
  19. package/dist/components/embed-realtime/context.d.ts +1 -1
  20. package/dist/contexts/EditorContext.d.ts +1 -1
  21. package/dist/default-editor/SnowconeEditor.d.ts +2 -1
  22. package/dist/design/design-builder.d.ts +2 -2
  23. package/dist/{design-builder-DMKx9UXD.js → design-builder-CPF60RU2.js} +1 -2
  24. package/dist/design-builder-CPF60RU2.js.map +1 -0
  25. package/dist/editorErrors-CK1RISFa.js.map +1 -1
  26. package/dist/{embed-uALcVSYN.js → embed-2tp6Ue90.js} +4 -4
  27. package/dist/embed-2tp6Ue90.js.map +1 -0
  28. package/dist/embed.mjs +3 -3
  29. package/dist/index.mjs +3 -3
  30. package/dist/{template-fields-B9RagVpT.js → template-fields-FgImixyv.js} +2 -2
  31. package/dist/{template-fields-B9RagVpT.js.map → template-fields-FgImixyv.js.map} +1 -1
  32. package/dist/testing/assertRenders.d.ts +2 -1
  33. package/dist/testing.mjs +1 -1
  34. package/dist/testing.mjs.map +1 -1
  35. package/example/README.md +179 -0
  36. package/example/index.html +12 -0
  37. package/example/package.json +24 -0
  38. package/example/src/Widget.tsx +62 -0
  39. package/example/src/main.tsx +9 -0
  40. package/example/tsconfig.json +14 -0
  41. package/example/vite.config.ts +11 -0
  42. package/llms.txt +45 -0
  43. package/package.json +6 -2
  44. package/dist/design-builder-DMKx9UXD.js.map +0 -1
  45. 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?** Read the complete agent-facing docs in one fetch — <https://developers.snowcone.app/llms-full.txt> — before integrating. It's the source of truth; grep it before assuming a field or behavior is undocumented.
4
-
5
- **Embeddable design editor for print-on-demand and product customization.**
6
-
7
- `<SnowconeCanvas />` is a self-contained React component that lets your shoppers
8
- compose the artwork that goes onto a product — text, images, transforms, and
9
- effects across one or more print areas. It's the same editor that powers Edit and
10
- Remix on [snowcone.app](https://snowcone.app). Drop it in, get the design state and
11
- print-ready exports back out.
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
- ## Quick start
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
- ## API at a glance
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-B01o5LmS.js";
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-DMKx9UXD.js";
6
- import { u as k } from "./embed-uALcVSYN.js";
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-UBKdG0c0.js.map
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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"}