@tangle-network/agent-app 0.26.0 → 0.28.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 (101) hide show
  1. package/dist/DesignCanvas-AOIVN3SJ.js +12 -0
  2. package/dist/DesignCanvasEditor-TN7CWUJW.js +13 -0
  3. package/dist/{TimelineEditor-JDG54OBP.js → TimelineEditor-Z7C6BFP5.js} +2 -2
  4. package/dist/{apply-Cp8c3K9D.d.ts → apply-Gk4raT2j.d.ts} +3 -1
  5. package/dist/brand-extraction/index.d.ts +212 -0
  6. package/dist/brand-extraction/index.js +446 -0
  7. package/dist/brand-extraction/index.js.map +1 -0
  8. package/dist/{chunk-AFDROJ64.js → chunk-2QI7XV2T.js} +1 -1
  9. package/dist/chunk-2QI7XV2T.js.map +1 -0
  10. package/dist/{chunk-LUE4HO5C.js → chunk-4IQMTTNE.js} +10 -10
  11. package/dist/chunk-4IQMTTNE.js.map +1 -0
  12. package/dist/{chunk-FSUR2752.js → chunk-7I37CO5D.js} +274 -388
  13. package/dist/chunk-7I37CO5D.js.map +1 -0
  14. package/dist/{chunk-5RT6KY4G.js → chunk-GQRL6VNU.js} +1 -1
  15. package/dist/chunk-GQRL6VNU.js.map +1 -0
  16. package/dist/{chunk-SCG5JJ4C.js → chunk-HFC4BTWJ.js} +32 -4
  17. package/dist/chunk-HFC4BTWJ.js.map +1 -0
  18. package/dist/{chunk-7QCIYDGC.js → chunk-IOREXWLM.js} +2 -2
  19. package/dist/chunk-J7JNV5BH.js +14 -0
  20. package/dist/chunk-J7JNV5BH.js.map +1 -0
  21. package/dist/{chunk-6UOE5CTA.js → chunk-JT6HHO5P.js} +5 -10
  22. package/dist/chunk-JT6HHO5P.js.map +1 -0
  23. package/dist/{chunk-JZAJE3JL.js → chunk-M2EBUDZ7.js} +19 -5
  24. package/dist/chunk-M2EBUDZ7.js.map +1 -0
  25. package/dist/{chunk-RJ2ZHK5D.js → chunk-S6WS2B32.js} +132 -7
  26. package/dist/chunk-S6WS2B32.js.map +1 -0
  27. package/dist/chunk-SQV7NUEM.js +136 -0
  28. package/dist/chunk-SQV7NUEM.js.map +1 -0
  29. package/dist/chunk-UAKFCMWK.js +1 -0
  30. package/dist/chunk-UAKFCMWK.js.map +1 -0
  31. package/dist/chunk-V5XZKHCL.js +296 -0
  32. package/dist/chunk-V5XZKHCL.js.map +1 -0
  33. package/dist/chunk-XWJXZ6WE.js +604 -0
  34. package/dist/chunk-XWJXZ6WE.js.map +1 -0
  35. package/dist/chunk-Y2SZVW35.js +125 -0
  36. package/dist/chunk-Y2SZVW35.js.map +1 -0
  37. package/dist/{chunk-LPAVBGVY.js → chunk-YEWTVDDT.js} +399 -860
  38. package/dist/chunk-YEWTVDDT.js.map +1 -0
  39. package/dist/{chunk-UDXMR3AD.js → chunk-YNALYIQ3.js} +56 -18
  40. package/dist/chunk-YNALYIQ3.js.map +1 -0
  41. package/dist/command-stack-CkjelfK_.d.ts +267 -0
  42. package/dist/design-canvas/drizzle.d.ts +2 -2
  43. package/dist/design-canvas/index.d.ts +7 -6
  44. package/dist/design-canvas/index.js +3 -2
  45. package/dist/design-canvas-react/engine.d.ts +349 -0
  46. package/dist/design-canvas-react/engine.js +86 -0
  47. package/dist/design-canvas-react/engine.js.map +1 -0
  48. package/dist/design-canvas-react/index.d.ts +80 -618
  49. package/dist/design-canvas-react/index.js +85 -172
  50. package/dist/design-canvas-react/index.js.map +1 -1
  51. package/dist/design-canvas-react/lazy.d.ts +6 -0
  52. package/dist/design-canvas-react/lazy.js +9 -0
  53. package/dist/design-canvas-react/lazy.js.map +1 -0
  54. package/dist/{export-presets-Dl5Aa5xj.d.ts → export-presets-DAsU5Wal.d.ts} +2 -143
  55. package/dist/flow-types-Cb_AblZs.d.ts +25 -0
  56. package/dist/index.d.ts +11 -8
  57. package/dist/index.js +21 -8
  58. package/dist/lazy-BHHqS8-j.d.ts +72 -0
  59. package/dist/missions/index.d.ts +12 -0
  60. package/dist/missions/index.js +1 -1
  61. package/dist/{model-BHLN208Z.d.ts → model-BBwyOuWG.d.ts} +3 -1
  62. package/dist/operations-DLIvvD00.d.ts +144 -0
  63. package/dist/platform/index.js +1 -1
  64. package/dist/runtime/index.d.ts +33 -33
  65. package/dist/runtime/index.js +1 -1
  66. package/dist/sequences/index.d.ts +1 -1
  67. package/dist/sequences/index.js +2 -1
  68. package/dist/sequences-react/index.d.ts +15 -1
  69. package/dist/sequences-react/index.js +2 -2
  70. package/dist/{store-CUStmtdH.d.ts → store-CFyxgTlD.d.ts} +1 -1
  71. package/dist/theme/index.d.ts +80 -0
  72. package/dist/theme/index.js +14 -0
  73. package/dist/theme/index.js.map +1 -0
  74. package/dist/theme/tailwind-preset.d.ts +65 -0
  75. package/dist/theme/tailwind-preset.js +33 -0
  76. package/dist/theme/tailwind-preset.js.map +1 -0
  77. package/dist/theme/tokens.css +95 -0
  78. package/dist/trace/index.d.ts +2 -18
  79. package/dist/trace/index.js +2 -2
  80. package/dist/web/index.d.ts +25 -2
  81. package/dist/web/index.js +3 -1
  82. package/dist/web-react/index.d.ts +71 -31
  83. package/dist/web-react/index.js +625 -486
  84. package/dist/web-react/index.js.map +1 -1
  85. package/package.json +33 -3
  86. package/dist/DesignCanvas-MA5EH324.js +0 -10
  87. package/dist/DesignCanvasEditor-YLCO7I5I.js +0 -9
  88. package/dist/chunk-5RT6KY4G.js.map +0 -1
  89. package/dist/chunk-6UOE5CTA.js.map +0 -1
  90. package/dist/chunk-AFDROJ64.js.map +0 -1
  91. package/dist/chunk-FSUR2752.js.map +0 -1
  92. package/dist/chunk-JZAJE3JL.js.map +0 -1
  93. package/dist/chunk-LPAVBGVY.js.map +0 -1
  94. package/dist/chunk-LUE4HO5C.js.map +0 -1
  95. package/dist/chunk-RJ2ZHK5D.js.map +0 -1
  96. package/dist/chunk-SCG5JJ4C.js.map +0 -1
  97. package/dist/chunk-UDXMR3AD.js.map +0 -1
  98. /package/dist/{DesignCanvas-MA5EH324.js.map → DesignCanvas-AOIVN3SJ.js.map} +0 -0
  99. /package/dist/{DesignCanvasEditor-YLCO7I5I.js.map → DesignCanvasEditor-TN7CWUJW.js.map} +0 -0
  100. /package/dist/{TimelineEditor-JDG54OBP.js.map → TimelineEditor-Z7C6BFP5.js.map} +0 -0
  101. /package/dist/{chunk-7QCIYDGC.js.map → chunk-IOREXWLM.js.map} +0 -0
@@ -1,564 +1,29 @@
1
+ import {
2
+ addPageCommand,
3
+ bindSlotCommand,
4
+ createSceneCommandStack,
5
+ deleteElementCommand,
6
+ deletePageCommand,
7
+ duplicatePageCommand,
8
+ groupElementsCommand,
9
+ multiSetAttrsCommand,
10
+ reorderElementCommand,
11
+ reorderPageCommand,
12
+ setAttrsCommand,
13
+ setPageGuidesCommand,
14
+ setPagePropsCommand,
15
+ ungroupElementCommand
16
+ } from "./chunk-XWJXZ6WE.js";
1
17
  import {
2
18
  SIZE_PRESETS,
3
- applySceneOperation,
4
- applySceneOperations,
5
19
  findElement,
6
20
  matchPreset,
7
- requireElement,
8
21
  requirePage
9
- } from "./chunk-JZAJE3JL.js";
22
+ } from "./chunk-M2EBUDZ7.js";
10
23
 
11
24
  // src/design-canvas-react/components/DesignCanvas.tsx
12
25
  import { useCallback as useCallback2, useEffect as useEffect3, useMemo, useRef as useRef5, useState as useState5, useSyncExternalStore } from "react";
13
26
 
14
- // src/design-canvas-react/engine/command-stack.ts
15
- var SCENE_COMMAND_HISTORY_LIMIT = 200;
16
- function createSceneCommandStack(document2, activePageId) {
17
- let state = {
18
- document: document2,
19
- activePageId,
20
- selectedElementIds: [],
21
- zoom: 1,
22
- panX: 0,
23
- panY: 0,
24
- gridEnabled: false,
25
- gridSize: 10,
26
- snapEnabled: true,
27
- showRulers: true,
28
- showBleed: false
29
- };
30
- const undoStack = [];
31
- const redoStack = [];
32
- const listeners = /* @__PURE__ */ new Set();
33
- const notify = () => {
34
- for (const listener of [...listeners]) listener();
35
- };
36
- return {
37
- execute(command) {
38
- state = command.execute(state);
39
- undoStack.push(command);
40
- if (undoStack.length > SCENE_COMMAND_HISTORY_LIMIT) {
41
- undoStack.splice(0, undoStack.length - SCENE_COMMAND_HISTORY_LIMIT);
42
- }
43
- redoStack.length = 0;
44
- notify();
45
- },
46
- // Both transforms run BEFORE the stacks move: a throwing transform (e.g.
47
- // missing element after reset()) leaves history and state exactly as they
48
- // were. The entry is never silently destroyed — the caller can retry after
49
- // the next server refresh restores the target.
50
- undo() {
51
- const command = undoStack[undoStack.length - 1];
52
- if (!command) throw new Error("nothing to undo \u2014 guard with canUndo() before calling undo()");
53
- state = command.undo(state);
54
- undoStack.pop();
55
- redoStack.push(command);
56
- notify();
57
- return command;
58
- },
59
- redo() {
60
- const command = redoStack[redoStack.length - 1];
61
- if (!command) throw new Error("nothing to redo \u2014 guard with canRedo() before calling redo()");
62
- state = command.execute(state);
63
- redoStack.pop();
64
- undoStack.push(command);
65
- notify();
66
- return command;
67
- },
68
- canUndo() {
69
- return undoStack.length > 0;
70
- },
71
- canRedo() {
72
- return redoStack.length > 0;
73
- },
74
- subscribe(listener) {
75
- listeners.add(listener);
76
- return () => {
77
- listeners.delete(listener);
78
- };
79
- },
80
- getState() {
81
- return state;
82
- },
83
- setView(patch) {
84
- state = { ...state, ...patch };
85
- notify();
86
- },
87
- rollback(command) {
88
- const idx = undoStack.lastIndexOf(command);
89
- if (idx === -1) return;
90
- state = command.undo(state);
91
- undoStack.splice(idx, 1);
92
- redoStack.length = 0;
93
- notify();
94
- },
95
- /** Rebase onto a server-refreshed document. History survives; selection
96
- * drops ids the refresh removed so view state never dangles. */
97
- reset(newDocument) {
98
- const liveElementIds = /* @__PURE__ */ new Set();
99
- for (const page of newDocument.pages) {
100
- collectElementIds(page.elements, liveElementIds);
101
- }
102
- const activePageExists = newDocument.pages.some((p) => p.id === state.activePageId);
103
- const activePageId2 = activePageExists ? state.activePageId : newDocument.pages[0]?.id ?? state.activePageId;
104
- state = {
105
- ...state,
106
- document: newDocument,
107
- activePageId: activePageId2,
108
- selectedElementIds: state.selectedElementIds.filter((id) => liveElementIds.has(id))
109
- };
110
- notify();
111
- }
112
- };
113
- }
114
- function collectElementIds(elements, ids) {
115
- for (const el of elements) {
116
- ids.add(el.id);
117
- if (el.kind === "group" && Array.isArray(el.children)) {
118
- collectElementIds(el.children, ids);
119
- }
120
- }
121
- }
122
-
123
- // src/design-canvas-react/engine/commands.ts
124
- function applyOps(state, ops) {
125
- return { ...state, document: applySceneOperations(state.document, ops) };
126
- }
127
- function applyOp(state, op) {
128
- return { ...state, document: applySceneOperation(state.document, op) };
129
- }
130
- function addElementCommand(input) {
131
- const addOp = {
132
- type: "add_element",
133
- pageId: input.pageId,
134
- element: structuredClone(input.element),
135
- ...input.index !== void 0 ? { index: input.index } : {},
136
- ...input.parentGroupId !== void 0 ? { parentGroupId: input.parentGroupId } : {}
137
- };
138
- const deleteOp = {
139
- type: "delete_element",
140
- pageId: input.pageId,
141
- elementId: input.element.id
142
- };
143
- return {
144
- label: `Add ${input.element.kind}`,
145
- execute: (state) => applyOp(state, addOp),
146
- undo: (state) => applyOp(state, deleteOp),
147
- operations: () => [structuredClone(addOp)],
148
- inverseOperations: () => [structuredClone(deleteOp)]
149
- };
150
- }
151
- function setAttrsCommand(input) {
152
- const forwardOp = {
153
- type: "set_attrs",
154
- pageId: input.pageId,
155
- elementId: input.elementId,
156
- attrs: structuredClone(input.attrs)
157
- };
158
- const inverseOp = {
159
- type: "set_attrs",
160
- pageId: input.pageId,
161
- elementId: input.elementId,
162
- attrs: structuredClone(input.priorAttrs)
163
- };
164
- return {
165
- label: "Edit element",
166
- execute: (state) => applyOp(state, forwardOp),
167
- undo: (state) => applyOp(state, inverseOp),
168
- operations: () => [structuredClone(forwardOp)],
169
- inverseOperations: () => [structuredClone(inverseOp)]
170
- };
171
- }
172
- function multiSetAttrsCommand(entries) {
173
- if (entries.length === 0) throw new Error("multiSetAttrsCommand: entries must not be empty");
174
- const forwardOps = entries.map((e) => ({
175
- type: "set_attrs",
176
- pageId: e.pageId,
177
- elementId: e.elementId,
178
- attrs: structuredClone(e.attrs)
179
- }));
180
- const inverseOps = entries.map((e) => ({
181
- type: "set_attrs",
182
- pageId: e.pageId,
183
- elementId: e.elementId,
184
- attrs: structuredClone(e.priorAttrs)
185
- }));
186
- return {
187
- label: `Edit ${entries.length} elements`,
188
- execute: (state) => applyOps(state, forwardOps),
189
- undo: (state) => applyOps(state, inverseOps),
190
- operations: () => structuredClone(forwardOps),
191
- inverseOperations: () => structuredClone(inverseOps)
192
- };
193
- }
194
- function reorderElementCommand(input) {
195
- let capturedFromIndex = null;
196
- const forwardOp = {
197
- type: "reorder_element",
198
- pageId: input.pageId,
199
- elementId: input.elementId,
200
- toIndex: input.toIndex
201
- };
202
- return {
203
- label: "Reorder element",
204
- execute: (state) => {
205
- if (capturedFromIndex === null) {
206
- const page = requirePage(state.document, input.pageId);
207
- const { index } = requireElement(page, input.elementId);
208
- capturedFromIndex = index;
209
- }
210
- return applyOp(state, forwardOp);
211
- },
212
- undo: (state) => {
213
- if (capturedFromIndex === null) {
214
- throw new Error("reorderElementCommand: undo called before execute");
215
- }
216
- return applyOp(state, {
217
- type: "reorder_element",
218
- pageId: input.pageId,
219
- elementId: input.elementId,
220
- toIndex: capturedFromIndex
221
- });
222
- },
223
- operations: () => [structuredClone(forwardOp)],
224
- inverseOperations: () => {
225
- if (capturedFromIndex === null) {
226
- throw new Error("reorderElementCommand: inverseOperations called before execute");
227
- }
228
- return [{
229
- type: "reorder_element",
230
- pageId: input.pageId,
231
- elementId: input.elementId,
232
- toIndex: capturedFromIndex
233
- }];
234
- }
235
- };
236
- }
237
- function deleteElementCommand(input) {
238
- const page = requirePage(input.document, input.pageId);
239
- const { element, owner, index } = requireElement(page, input.elementId);
240
- const snapshot = structuredClone(element);
241
- const parentGroupId = findParentGroupId(page, owner);
242
- const deleteOp = {
243
- type: "delete_element",
244
- pageId: input.pageId,
245
- elementId: input.elementId
246
- };
247
- const addOp = {
248
- type: "add_element",
249
- pageId: input.pageId,
250
- element: snapshot,
251
- index,
252
- ...parentGroupId !== void 0 ? { parentGroupId } : {}
253
- };
254
- return {
255
- label: `Delete ${element.kind}`,
256
- execute: (state) => {
257
- const next = applyOp(state, deleteOp);
258
- return {
259
- ...next,
260
- selectedElementIds: next.selectedElementIds.filter((id) => id !== input.elementId)
261
- };
262
- },
263
- undo: (state) => applyOp(state, addOp),
264
- operations: () => [structuredClone(deleteOp)],
265
- inverseOperations: () => [structuredClone(addOp)]
266
- };
267
- }
268
- function findParentGroupId(page, owner) {
269
- if (owner === page.elements) return void 0;
270
- return findGroupWithChildren(page.elements, owner);
271
- }
272
- function findGroupWithChildren(elements, target) {
273
- for (const el of elements) {
274
- if (el.kind === "group") {
275
- if (el.children === target) return el.id;
276
- const found = findGroupWithChildren(el.children, target);
277
- if (found !== void 0) return found;
278
- }
279
- }
280
- return void 0;
281
- }
282
- function groupElementsCommand(input) {
283
- if (input.elementIds.length < 2) {
284
- throw new Error("groupElementsCommand: requires \u2265 2 elementIds");
285
- }
286
- const groupOp = {
287
- type: "group_elements",
288
- pageId: input.pageId,
289
- elementIds: input.elementIds.slice(),
290
- groupId: input.groupId,
291
- ...input.name !== void 0 ? { name: input.name } : {}
292
- };
293
- const ungroupOp = {
294
- type: "ungroup_element",
295
- pageId: input.pageId,
296
- groupId: input.groupId
297
- };
298
- return {
299
- label: `Group ${input.elementIds.length} elements`,
300
- execute: (state) => {
301
- const next = applyOp(state, groupOp);
302
- return {
303
- ...next,
304
- selectedElementIds: [input.groupId]
305
- };
306
- },
307
- undo: (state) => {
308
- const next = applyOp(state, ungroupOp);
309
- return { ...next, selectedElementIds: input.elementIds.slice() };
310
- },
311
- operations: () => [structuredClone(groupOp)],
312
- inverseOperations: () => [structuredClone(ungroupOp)]
313
- };
314
- }
315
- function ungroupElementCommand(input) {
316
- const page = requirePage(input.document, input.pageId);
317
- const { element } = requireElement(page, input.groupId);
318
- if (element.kind !== "group") {
319
- throw new Error(`ungroupElementCommand: element ${input.groupId} is kind ${element.kind}, not group`);
320
- }
321
- const childIds = element.children.map((c) => c.id);
322
- const ungroupOp = {
323
- type: "ungroup_element",
324
- pageId: input.pageId,
325
- groupId: input.groupId
326
- };
327
- const regroupOp = {
328
- type: "group_elements",
329
- pageId: input.pageId,
330
- elementIds: childIds,
331
- groupId: input.groupId,
332
- name: element.name
333
- };
334
- return {
335
- label: `Ungroup`,
336
- execute: (state) => {
337
- const next = applyOp(state, ungroupOp);
338
- return { ...next, selectedElementIds: childIds.slice() };
339
- },
340
- undo: (state) => {
341
- const next = applyOp(state, regroupOp);
342
- return { ...next, selectedElementIds: [input.groupId] };
343
- },
344
- operations: () => [structuredClone(ungroupOp)],
345
- inverseOperations: () => [structuredClone(regroupOp)]
346
- };
347
- }
348
- function addPageCommand(input) {
349
- const addOp = {
350
- type: "add_page",
351
- pageId: input.pageId,
352
- ...input.options !== void 0 ? { options: input.options } : {},
353
- ...input.index !== void 0 ? { index: input.index } : {}
354
- };
355
- const deleteOp = { type: "delete_page", pageId: input.pageId };
356
- return {
357
- label: "Add page",
358
- execute: (state) => {
359
- const next = applyOp(state, addOp);
360
- return { ...next, activePageId: input.pageId };
361
- },
362
- undo: (state) => {
363
- const next = applyOp(state, deleteOp);
364
- const activeExists = next.document.pages.some((p) => p.id === next.activePageId);
365
- return activeExists ? next : { ...next, activePageId: next.document.pages[0].id };
366
- },
367
- operations: () => [structuredClone(addOp)],
368
- inverseOperations: () => [structuredClone(deleteOp)]
369
- };
370
- }
371
- function duplicatePageCommand(input) {
372
- requirePage(input.document, input.sourcePageId);
373
- const dupOp = {
374
- type: "duplicate_page",
375
- sourcePageId: input.sourcePageId,
376
- pageId: input.pageId
377
- };
378
- const deleteOp = { type: "delete_page", pageId: input.pageId };
379
- return {
380
- label: "Duplicate page",
381
- execute: (state) => {
382
- const next = applyOp(state, dupOp);
383
- return { ...next, activePageId: input.pageId };
384
- },
385
- undo: (state) => {
386
- const next = applyOp(state, deleteOp);
387
- const activeExists = next.document.pages.some((p) => p.id === next.activePageId);
388
- return activeExists ? next : { ...next, activePageId: input.sourcePageId };
389
- },
390
- operations: () => [structuredClone(dupOp)],
391
- inverseOperations: () => [structuredClone(deleteOp)]
392
- };
393
- }
394
- function deletePageCommand(input) {
395
- if (input.document.pages.length <= 1) {
396
- throw new Error("deletePageCommand: cannot delete the last page");
397
- }
398
- const page = requirePage(input.document, input.pageId);
399
- const currentIndex = input.document.pages.findIndex((p) => p.id === input.pageId);
400
- const snapshot = structuredClone(page);
401
- const deleteOp = { type: "delete_page", pageId: input.pageId };
402
- function buildRestoreOps() {
403
- const ops = [
404
- {
405
- type: "add_page",
406
- pageId: snapshot.id,
407
- options: {
408
- name: snapshot.name,
409
- width: snapshot.width,
410
- height: snapshot.height,
411
- background: snapshot.background
412
- },
413
- index: currentIndex
414
- }
415
- ];
416
- if (snapshot.bleed) {
417
- ops.push({ type: "set_page_props", pageId: snapshot.id, bleed: snapshot.bleed });
418
- }
419
- if (snapshot.guides.vertical.length > 0 || snapshot.guides.horizontal.length > 0) {
420
- ops.push({ type: "set_page_guides", pageId: snapshot.id, guides: snapshot.guides });
421
- }
422
- for (let i = 0; i < snapshot.elements.length; i++) {
423
- ops.push({
424
- type: "add_element",
425
- pageId: snapshot.id,
426
- element: structuredClone(snapshot.elements[i]),
427
- index: i
428
- });
429
- }
430
- return ops;
431
- }
432
- return {
433
- label: "Delete page",
434
- execute: (state) => {
435
- const next = applyOp(state, deleteOp);
436
- const activeExists = next.document.pages.some((p) => p.id === next.activePageId);
437
- if (activeExists) return next;
438
- const fallbackIndex = Math.min(currentIndex, next.document.pages.length - 1);
439
- return { ...next, activePageId: next.document.pages[fallbackIndex].id };
440
- },
441
- undo: (state) => {
442
- const next = applyOps(state, buildRestoreOps());
443
- return { ...next, activePageId: input.pageId };
444
- },
445
- operations: () => [structuredClone(deleteOp)],
446
- inverseOperations: () => buildRestoreOps()
447
- };
448
- }
449
- function reorderPageCommand(input) {
450
- let capturedFromIndex = null;
451
- const forwardOp = {
452
- type: "reorder_page",
453
- pageId: input.pageId,
454
- toIndex: input.toIndex
455
- };
456
- return {
457
- label: "Reorder page",
458
- execute: (state) => {
459
- if (capturedFromIndex === null) {
460
- capturedFromIndex = state.document.pages.findIndex((p) => p.id === input.pageId);
461
- if (capturedFromIndex === -1) throw new Error(`reorderPageCommand: page ${input.pageId} not found`);
462
- }
463
- return applyOp(state, forwardOp);
464
- },
465
- undo: (state) => {
466
- if (capturedFromIndex === null) throw new Error("reorderPageCommand: undo called before execute");
467
- return applyOp(state, {
468
- type: "reorder_page",
469
- pageId: input.pageId,
470
- toIndex: capturedFromIndex
471
- });
472
- },
473
- operations: () => [structuredClone(forwardOp)],
474
- inverseOperations: () => {
475
- if (capturedFromIndex === null) throw new Error("reorderPageCommand: inverseOperations called before execute");
476
- return [{ type: "reorder_page", pageId: input.pageId, toIndex: capturedFromIndex }];
477
- }
478
- };
479
- }
480
- function setPagePropsCommand(input) {
481
- const page = requirePage(input.document, input.pageId);
482
- const prior = {
483
- type: "set_page_props",
484
- pageId: input.pageId,
485
- ...input.props.name !== void 0 ? { name: page.name } : {},
486
- ...input.props.width !== void 0 ? { width: page.width } : {},
487
- ...input.props.height !== void 0 ? { height: page.height } : {},
488
- ...input.props.background !== void 0 ? { background: page.background } : {},
489
- ...input.props.bleed !== void 0 ? { bleed: page.bleed } : {}
490
- };
491
- const forwardOp = {
492
- type: "set_page_props",
493
- pageId: input.pageId,
494
- ...input.props
495
- };
496
- return {
497
- label: "Edit page",
498
- execute: (state) => applyOp(state, forwardOp),
499
- undo: (state) => applyOp(state, prior),
500
- operations: () => [structuredClone(forwardOp)],
501
- inverseOperations: () => [structuredClone(prior)]
502
- };
503
- }
504
- function setPageGuidesCommand(input) {
505
- const page = requirePage(input.document, input.pageId);
506
- const priorGuides = structuredClone(page.guides);
507
- const forwardOp = {
508
- type: "set_page_guides",
509
- pageId: input.pageId,
510
- guides: structuredClone(input.guides)
511
- };
512
- const inverseOp = {
513
- type: "set_page_guides",
514
- pageId: input.pageId,
515
- guides: priorGuides
516
- };
517
- return {
518
- label: "Edit guides",
519
- execute: (state) => applyOp(state, forwardOp),
520
- undo: (state) => applyOp(state, inverseOp),
521
- operations: () => [structuredClone(forwardOp)],
522
- inverseOperations: () => [structuredClone(inverseOp)]
523
- };
524
- }
525
- function bindSlotCommand(input) {
526
- const page = requirePage(input.document, input.pageId);
527
- const { element } = requireElement(page, input.elementId);
528
- const priorSlot = element.slot ?? null;
529
- const forwardOp = {
530
- type: "bind_slot",
531
- pageId: input.pageId,
532
- elementId: input.elementId,
533
- slot: input.slot
534
- };
535
- const inverseOp = {
536
- type: "bind_slot",
537
- pageId: input.pageId,
538
- elementId: input.elementId,
539
- slot: priorSlot
540
- };
541
- return {
542
- label: input.slot === null ? "Unbind slot" : `Bind slot "${input.slot}"`,
543
- execute: (state) => applyOp(state, forwardOp),
544
- undo: (state) => applyOp(state, inverseOp),
545
- operations: () => [structuredClone(forwardOp)],
546
- inverseOperations: () => [structuredClone(inverseOp)]
547
- };
548
- }
549
- function setDocumentTitleCommand(input) {
550
- const priorTitle = input.document.title;
551
- const forwardOp = { type: "set_document_title", title: input.title };
552
- const inverseOp = { type: "set_document_title", title: priorTitle };
553
- return {
554
- label: `Rename document`,
555
- execute: (state) => applyOp(state, forwardOp),
556
- undo: (state) => applyOp(state, inverseOp),
557
- operations: () => [structuredClone(forwardOp)],
558
- inverseOperations: () => [structuredClone(inverseOp)]
559
- };
560
- }
561
-
562
27
  // src/design-canvas-react/components/ruler-math.ts
563
28
  var TICK_STEP_CANDIDATES_PX = [1, 2, 5, 10, 25, 50, 100, 250, 500, 1e3, 2500, 5e3];
564
29
  function selectTickStep(input) {
@@ -731,6 +196,7 @@ function glyph(paths) {
731
196
  }
732
197
  var UndoGlyph = glyph(/* @__PURE__ */ jsx2("path", { d: "M3 7v6h6M3 13a9 9 0 1 0 3-7.7" }));
733
198
  var RedoGlyph = glyph(/* @__PURE__ */ jsx2("path", { d: "M21 7v6h-6M21 13a9 9 0 1 1-3-7.7" }));
199
+ var SwapGlyph = glyph(/* @__PURE__ */ jsx2("path", { d: "M7 4 3 8l4 4M3 8h14M17 20l4-4-4-4M21 16H7" }));
734
200
  var EyeGlyph = glyph(
735
201
  /* @__PURE__ */ jsxs2(Fragment2, { children: [
736
202
  /* @__PURE__ */ jsx2("path", { d: "M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7z" }),
@@ -795,7 +261,6 @@ var BoldGlyph = glyph(/* @__PURE__ */ jsx2("path", { d: "M6 4h8a4 4 0 0 1 0 8H6z
795
261
  var ItalicGlyph = glyph(/* @__PURE__ */ jsx2("path", { d: "M11 4h6M7 20h6M14 4 8 20" }));
796
262
  var PlusGlyph = glyph(/* @__PURE__ */ jsx2("path", { d: "M12 5v14M5 12h14" }));
797
263
  var ChevronDownGlyph = glyph(/* @__PURE__ */ jsx2("path", { d: "m6 9 6 6 6-6" }));
798
- var ChevronRightGlyph = glyph(/* @__PURE__ */ jsx2("path", { d: "m9 18 6-6-6-6" }));
799
264
  var RectGlyph = glyph(/* @__PURE__ */ jsx2("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }));
800
265
  var EllipseGlyph = glyph(/* @__PURE__ */ jsx2("ellipse", { cx: "12", cy: "12", rx: "10", ry: "7" }));
801
266
  var LineGlyph = glyph(/* @__PURE__ */ jsx2("path", { d: "M5 19 19 5" }));
@@ -819,12 +284,6 @@ var SlotGlyph = glyph(
819
284
  /* @__PURE__ */ jsx2("path", { d: "M12 1v4M12 19v4M4.2 4.2l2.8 2.8M17 17l2.8 2.8M1 12h4M19 12h4M4.2 19.8l2.8-2.8M17 7 19.8 4.2" })
820
285
  ] })
821
286
  );
822
- var FitGlyph = glyph(
823
- /* @__PURE__ */ jsxs2(Fragment2, { children: [
824
- /* @__PURE__ */ jsx2("path", { d: "M3 3h4v4H3zM17 3h4v4h-4zM3 17h4v4H3zM17 17h4v4h-4z" }),
825
- /* @__PURE__ */ jsx2("path", { d: "M7 5h10M5 7v10M19 7v10M7 19h10" })
826
- ] })
827
- );
828
287
  var PageGlyph = glyph(
829
288
  /* @__PURE__ */ jsxs2(Fragment2, { children: [
830
289
  /* @__PURE__ */ jsx2("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
@@ -873,11 +332,24 @@ var ShapesGlyph = glyph(
873
332
  ] })
874
333
  );
875
334
 
335
+ // src/design-canvas-react/components/icon-button.tsx
336
+ import { forwardRef } from "react";
337
+ import { jsx as jsx3 } from "react/jsx-runtime";
338
+ var BTN_BASE = "items-center justify-center rounded border border-[var(--border-default)] text-[var(--text-secondary)] transition hover:text-[var(--text-primary)] focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--ring))] focus-visible:ring-offset-1 focus-visible:ring-offset-[var(--bg-input)] disabled:cursor-default disabled:opacity-40";
339
+ var BTN_ACTIVE_EXTRA = " border-[var(--brand-primary)] text-[var(--brand-primary)] hover:text-[var(--brand-primary)]";
340
+ var BTN = `flex h-7 w-7 ${BTN_BASE}`;
341
+ var BTN_ACTIVE = BTN + BTN_ACTIVE_EXTRA;
342
+ var BTN_SM = `flex h-6 w-6 ${BTN_BASE}`;
343
+ var BTN_SM_ACTIVE = BTN_SM + BTN_ACTIVE_EXTRA;
344
+ var IconButton = forwardRef(function IconButton2({ active = false, size = "md", className = "", type = "button", ...rest }, ref) {
345
+ const base = size === "sm" ? active ? BTN_SM_ACTIVE : BTN_SM : active ? BTN_ACTIVE : BTN;
346
+ return /* @__PURE__ */ jsx3("button", { ref, type, className: className ? `${base} ${className}` : base, ...rest });
347
+ });
348
+
876
349
  // src/design-canvas-react/components/PagesStrip.tsx
877
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
350
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
878
351
  var THUMBNAIL_W = 80;
879
352
  var THUMBNAIL_H = 56;
880
- var BTN = "flex h-6 w-6 items-center justify-center rounded border border-[var(--border-default)] text-[var(--text-secondary)] transition hover:text-[var(--text-primary)] disabled:cursor-default disabled:opacity-40";
881
353
  function PagesStrip({
882
354
  pages,
883
355
  activePageId,
@@ -887,7 +359,8 @@ function PagesStrip({
887
359
  onAddPage,
888
360
  onDuplicatePage,
889
361
  onDeletePage,
890
- onReorderPage
362
+ onReorderPage,
363
+ canManagePages = true
891
364
  }) {
892
365
  const [thumbnails, setThumbnails] = useState({});
893
366
  const thumbnailVersionRef = useRef(0);
@@ -927,10 +400,6 @@ function PagesStrip({
927
400
  return /* @__PURE__ */ jsxs3(
928
401
  "div",
929
402
  {
930
- role: "button",
931
- tabIndex: 0,
932
- "aria-label": `Page ${index + 1}: ${page.name}${isActive ? " (active)" : ""}`,
933
- "aria-pressed": isActive,
934
403
  draggable: canWrite,
935
404
  onDragStart: () => {
936
405
  dragIndexRef.current = index;
@@ -953,38 +422,43 @@ function PagesStrip({
953
422
  dragIndexRef.current = null;
954
423
  setDragOverIndex(null);
955
424
  },
956
- onClick: () => onSelectPage(page.id),
957
- onKeyDown: (event) => {
958
- if (event.key === "Enter" || event.key === " ") {
959
- event.preventDefault();
960
- onSelectPage(page.id);
961
- }
962
- },
963
425
  className: [
964
- "group relative flex shrink-0 cursor-pointer flex-col items-center gap-1 rounded p-1 transition",
426
+ "group relative flex shrink-0 flex-col items-center gap-1 rounded p-1 transition",
965
427
  isActive ? "ring-2 ring-[var(--brand-primary)]" : "hover:bg-[var(--border-default)]/40",
966
428
  dragOverIndex === index ? "ring-1 ring-[var(--brand-primary)]/60" : ""
967
429
  ].join(" "),
968
430
  children: [
969
- /* @__PURE__ */ jsx3(
970
- "div",
431
+ /* @__PURE__ */ jsxs3(
432
+ "button",
971
433
  {
972
- className: "overflow-hidden rounded border border-[var(--border-default)] bg-white",
973
- style: { width: THUMBNAIL_W, height: THUMBNAIL_H },
974
- children: thumbUrl ? /* @__PURE__ */ jsx3(
975
- "img",
976
- {
977
- src: thumbUrl,
978
- alt: page.name,
979
- className: "h-full w-full object-cover",
980
- draggable: false
981
- }
982
- ) : /* @__PURE__ */ jsx3("div", { className: "flex h-full w-full items-center justify-center", children: /* @__PURE__ */ jsx3(PageGlyph, { className: "h-5 w-5 text-[var(--text-muted)]" }) })
434
+ type: "button",
435
+ "aria-label": `Page ${index + 1}: ${page.name}${isActive ? " (active)" : ""}`,
436
+ "aria-pressed": isActive,
437
+ onClick: () => onSelectPage(page.id),
438
+ className: "flex cursor-pointer flex-col items-center gap-1 rounded focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--ring))]",
439
+ children: [
440
+ /* @__PURE__ */ jsx4(
441
+ "div",
442
+ {
443
+ className: "overflow-hidden rounded border border-[var(--border-default)] bg-[hsl(var(--card))]",
444
+ style: { width: THUMBNAIL_W, height: THUMBNAIL_H },
445
+ children: thumbUrl ? /* @__PURE__ */ jsx4(
446
+ "img",
447
+ {
448
+ src: thumbUrl,
449
+ alt: page.name,
450
+ className: "h-full w-full object-cover",
451
+ draggable: false
452
+ }
453
+ ) : /* @__PURE__ */ jsx4("div", { className: "flex h-full w-full items-center justify-center", children: /* @__PURE__ */ jsx4(PageGlyph, { className: "h-5 w-5 text-[var(--text-muted)]" }) })
454
+ }
455
+ ),
456
+ /* @__PURE__ */ jsx4("span", { className: "max-w-[80px] truncate text-[10px] text-[var(--text-secondary)]", children: page.name })
457
+ ]
983
458
  }
984
459
  ),
985
- /* @__PURE__ */ jsx3("span", { className: "max-w-[80px] truncate text-[10px] text-[var(--text-secondary)]", children: page.name }),
986
- canWrite ? /* @__PURE__ */ jsxs3("div", { className: "pointer-events-none absolute -top-1 right-0 flex gap-0.5 opacity-0 transition-opacity group-hover:pointer-events-auto group-hover:opacity-100", children: [
987
- /* @__PURE__ */ jsx3(
460
+ canWrite && canManagePages ? /* @__PURE__ */ jsxs3("div", { className: "pointer-events-none absolute -top-1 right-0 flex gap-0.5 opacity-0 transition-opacity group-hover:pointer-events-auto group-hover:opacity-100", children: [
461
+ /* @__PURE__ */ jsx4(
988
462
  "button",
989
463
  {
990
464
  type: "button",
@@ -993,11 +467,11 @@ function PagesStrip({
993
467
  event.stopPropagation();
994
468
  onDuplicatePage(page.id);
995
469
  },
996
- className: BTN,
997
- children: /* @__PURE__ */ jsx3(DuplicateGlyph, { className: "h-3 w-3" })
470
+ className: BTN_SM,
471
+ children: /* @__PURE__ */ jsx4(DuplicateGlyph, { className: "h-3 w-3" })
998
472
  }
999
473
  ),
1000
- /* @__PURE__ */ jsx3(
474
+ /* @__PURE__ */ jsx4(
1001
475
  "button",
1002
476
  {
1003
477
  type: "button",
@@ -1007,8 +481,8 @@ function PagesStrip({
1007
481
  event.stopPropagation();
1008
482
  if (pages.length > 1) onDeletePage(page.id);
1009
483
  },
1010
- className: BTN,
1011
- children: /* @__PURE__ */ jsx3(TrashGlyph, { className: "h-3 w-3 text-rose-400" })
484
+ className: BTN_SM,
485
+ children: /* @__PURE__ */ jsx4(TrashGlyph, { className: "h-3 w-3 text-[var(--text-danger)]" })
1012
486
  }
1013
487
  )
1014
488
  ] }) : null
@@ -1017,16 +491,16 @@ function PagesStrip({
1017
491
  page.id
1018
492
  );
1019
493
  }),
1020
- canWrite ? /* @__PURE__ */ jsxs3(
494
+ canWrite && canManagePages ? /* @__PURE__ */ jsxs3(
1021
495
  "button",
1022
496
  {
1023
497
  type: "button",
1024
498
  "aria-label": "Add page",
1025
499
  onClick: onAddPage,
1026
- className: "flex h-[72px] w-[80px] shrink-0 flex-col items-center justify-center gap-1 rounded border border-dashed border-[var(--border-default)] text-[var(--text-muted)] transition hover:border-[var(--brand-primary)] hover:text-[var(--brand-primary)]",
500
+ className: "flex h-[72px] w-[80px] shrink-0 flex-col items-center justify-center gap-1 rounded border border-dashed border-[var(--border-default)] text-[var(--text-muted)] transition hover:border-[var(--brand-primary)] hover:text-[var(--brand-primary)] focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--ring))]",
1027
501
  children: [
1028
- /* @__PURE__ */ jsx3(PlusGlyph, { className: "h-4 w-4" }),
1029
- /* @__PURE__ */ jsx3("span", { className: "text-[10px]", children: "Add page" })
502
+ /* @__PURE__ */ jsx4(PlusGlyph, { className: "h-4 w-4" }),
503
+ /* @__PURE__ */ jsx4("span", { className: "text-[10px]", children: "Add page" })
1030
504
  ]
1031
505
  }
1032
506
  ) : null
@@ -1037,20 +511,20 @@ function PagesStrip({
1037
511
 
1038
512
  // src/design-canvas-react/components/Rulers.tsx
1039
513
  import { useRef as useRef2, useState as useState2 } from "react";
1040
- import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
514
+ import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1041
515
  var RULER_SIZE_PX = 20;
1042
516
  var DELETE_THRESHOLD_PX = RULER_SIZE_PX + 4;
1043
517
  function Rulers({ pageWidth, pageHeight, zoom, scrollLeft, scrollTop, showRulers, guides, onGuidesChange }) {
1044
518
  if (!showRulers) return null;
1045
519
  return /* @__PURE__ */ jsxs4(Fragment3, { children: [
1046
- /* @__PURE__ */ jsx4(
520
+ /* @__PURE__ */ jsx5(
1047
521
  "div",
1048
522
  {
1049
523
  className: "absolute top-0 left-0 z-20 shrink-0 border-b border-r border-[var(--border-default)] bg-[var(--bg-input)]",
1050
524
  style: { width: RULER_SIZE_PX, height: RULER_SIZE_PX }
1051
525
  }
1052
526
  ),
1053
- /* @__PURE__ */ jsx4(
527
+ /* @__PURE__ */ jsx5(
1054
528
  HorizontalRuler,
1055
529
  {
1056
530
  pageWidth,
@@ -1060,7 +534,7 @@ function Rulers({ pageWidth, pageHeight, zoom, scrollLeft, scrollTop, showRulers
1060
534
  onGuidesChange
1061
535
  }
1062
536
  ),
1063
- /* @__PURE__ */ jsx4(
537
+ /* @__PURE__ */ jsx5(
1064
538
  VerticalRuler,
1065
539
  {
1066
540
  pageHeight,
@@ -1148,18 +622,18 @@ function HorizontalRuler({ pageWidth, zoom, scrollLeft, guides, onGuidesChange }
1148
622
  children: [
1149
623
  ticks.map((tick) => {
1150
624
  const screenX = tick.position * zoom - scrollLeft * zoom;
1151
- return /* @__PURE__ */ jsx4(
625
+ return /* @__PURE__ */ jsx5(
1152
626
  "div",
1153
627
  {
1154
628
  className: `absolute bottom-0 w-px bg-[var(--border-default)] ${tick.label !== null ? "top-1.5" : "top-[14px]"}`,
1155
629
  style: { left: screenX },
1156
- children: tick.label !== null ? /* @__PURE__ */ jsx4("span", { className: "absolute -top-1 left-0.5 whitespace-nowrap font-mono text-[9px] leading-none text-[var(--text-muted)]", children: tick.label }) : null
630
+ children: tick.label !== null ? /* @__PURE__ */ jsx5("span", { className: "absolute -top-1 left-0.5 whitespace-nowrap font-mono text-[9px] leading-none text-[var(--text-muted)]", children: tick.label }) : null
1157
631
  },
1158
632
  tick.position
1159
633
  );
1160
634
  }),
1161
- pointerX !== null ? /* @__PURE__ */ jsx4("div", { className: "pointer-events-none absolute top-0 bottom-0 w-px bg-[var(--brand-primary)]/60", style: { left: pointerX } }) : null,
1162
- dragGuideX !== null ? /* @__PURE__ */ jsx4(
635
+ pointerX !== null ? /* @__PURE__ */ jsx5("div", { className: "pointer-events-none absolute top-0 bottom-0 w-px bg-[var(--brand-primary)]/60", style: { left: pointerX } }) : null,
636
+ dragGuideX !== null ? /* @__PURE__ */ jsx5(
1163
637
  "div",
1164
638
  {
1165
639
  className: "pointer-events-none absolute top-0 bottom-0 w-px bg-[var(--brand-primary)]",
@@ -1242,12 +716,12 @@ function VerticalRuler({ pageHeight, zoom, scrollTop, guides, onGuidesChange })
1242
716
  children: [
1243
717
  ticks.map((tick) => {
1244
718
  const screenY = tick.position * zoom - scrollTop * zoom;
1245
- return /* @__PURE__ */ jsx4(
719
+ return /* @__PURE__ */ jsx5(
1246
720
  "div",
1247
721
  {
1248
722
  className: `absolute right-0 h-px bg-[var(--border-default)] ${tick.label !== null ? "left-1.5" : "left-[14px]"}`,
1249
723
  style: { top: screenY },
1250
- children: tick.label !== null ? /* @__PURE__ */ jsx4(
724
+ children: tick.label !== null ? /* @__PURE__ */ jsx5(
1251
725
  "span",
1252
726
  {
1253
727
  className: "absolute top-0.5 left-0 whitespace-nowrap font-mono text-[9px] leading-none text-[var(--text-muted)]",
@@ -1259,8 +733,8 @@ function VerticalRuler({ pageHeight, zoom, scrollTop, guides, onGuidesChange })
1259
733
  tick.position
1260
734
  );
1261
735
  }),
1262
- pointerY !== null ? /* @__PURE__ */ jsx4("div", { className: "pointer-events-none absolute left-0 right-0 h-px bg-[var(--brand-primary)]/60", style: { top: pointerY } }) : null,
1263
- dragGuideY !== null ? /* @__PURE__ */ jsx4(
736
+ pointerY !== null ? /* @__PURE__ */ jsx5("div", { className: "pointer-events-none absolute left-0 right-0 h-px bg-[var(--brand-primary)]/60", style: { top: pointerY } }) : null,
737
+ dragGuideY !== null ? /* @__PURE__ */ jsx5(
1264
738
  "div",
1265
739
  {
1266
740
  className: "pointer-events-none absolute left-0 right-0 h-px bg-[var(--brand-primary)]",
@@ -1274,7 +748,7 @@ function VerticalRuler({ pageHeight, zoom, scrollTop, guides, onGuidesChange })
1274
748
 
1275
749
  // src/design-canvas-react/components/Toolbar.tsx
1276
750
  import { useEffect as useEffect2, useRef as useRef3, useState as useState3 } from "react";
1277
- import { Fragment as Fragment4, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
751
+ import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1278
752
  var FONT_FAMILIES = [
1279
753
  "Inter",
1280
754
  "Arial",
@@ -1289,9 +763,7 @@ var FONT_FAMILIES = [
1289
763
  "Brush Script MT",
1290
764
  "Impact"
1291
765
  ];
1292
- var BTN2 = "flex h-7 w-7 items-center justify-center rounded border border-[var(--border-default)] text-[var(--text-secondary)] transition hover:text-[var(--text-primary)] disabled:cursor-default disabled:opacity-40";
1293
- var BTN_ACTIVE = `${BTN2} border-[var(--brand-primary)] text-[var(--brand-primary)] hover:text-[var(--brand-primary)]`;
1294
- var SEP = /* @__PURE__ */ jsx5("div", { className: "mx-1 h-5 w-px shrink-0 bg-[var(--border-default)]" });
766
+ var SEP = /* @__PURE__ */ jsx6("div", { className: "mx-1 h-5 w-px shrink-0 bg-[var(--border-default)]" });
1295
767
  var POPOVER_PANEL = "absolute top-full left-0 z-50 mt-1 flex flex-col rounded border border-[var(--border-default)] bg-[var(--bg-input)] shadow-lg";
1296
768
  function Popover({
1297
769
  open,
@@ -1336,8 +808,8 @@ function NumberInput({
1336
808
  setRaw(null);
1337
809
  }
1338
810
  return /* @__PURE__ */ jsxs5("label", { className: "flex flex-col items-center gap-0.5", children: [
1339
- /* @__PURE__ */ jsx5("span", { className: FIELD_LABEL, children: label }),
1340
- /* @__PURE__ */ jsx5(
811
+ /* @__PURE__ */ jsx6("span", { className: FIELD_LABEL, children: label }),
812
+ /* @__PURE__ */ jsx6(
1341
813
  "input",
1342
814
  {
1343
815
  type: "number",
@@ -1366,8 +838,8 @@ function SelectControl({
1366
838
  const [open, setOpen] = useState3(false);
1367
839
  const current = options.find((o) => o.value === value);
1368
840
  return /* @__PURE__ */ jsxs5("label", { className: "flex flex-col items-center gap-0.5", children: [
1369
- /* @__PURE__ */ jsx5("span", { className: FIELD_LABEL, children: label }),
1370
- /* @__PURE__ */ jsx5(
841
+ /* @__PURE__ */ jsx6("span", { className: FIELD_LABEL, children: label }),
842
+ /* @__PURE__ */ jsx6(
1371
843
  Popover,
1372
844
  {
1373
845
  open,
@@ -1382,12 +854,12 @@ function SelectControl({
1382
854
  onClick: () => setOpen((v) => !v),
1383
855
  className: `${buttonClassName} flex items-center justify-between gap-1 rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-2 py-0.5 text-left text-[12px] text-[var(--text-primary)] outline-none hover:border-[var(--brand-primary)] disabled:cursor-default disabled:opacity-40`,
1384
856
  children: [
1385
- /* @__PURE__ */ jsx5("span", { className: "truncate", children: current?.label ?? value }),
1386
- /* @__PURE__ */ jsx5("span", { className: "text-[8px] text-[var(--text-muted)]", children: "\u25BE" })
857
+ /* @__PURE__ */ jsx6("span", { className: "truncate", children: current?.label ?? value }),
858
+ /* @__PURE__ */ jsx6("span", { className: "text-[8px] text-[var(--text-muted)]", children: "\u25BE" })
1387
859
  ]
1388
860
  }
1389
861
  ),
1390
- children: /* @__PURE__ */ jsx5("div", { role: "listbox", className: `${POPOVER_PANEL} max-h-64 w-44 overflow-y-auto py-1`, children: options.map((opt) => /* @__PURE__ */ jsx5(
862
+ children: /* @__PURE__ */ jsx6("div", { role: "listbox", className: `${POPOVER_PANEL} max-h-64 w-44 overflow-y-auto py-1`, children: options.map((opt) => /* @__PURE__ */ jsx6(
1391
863
  "button",
1392
864
  {
1393
865
  type: "button",
@@ -1416,8 +888,8 @@ function FontPicker({
1416
888
  const families = FONT_FAMILIES.includes(value) ? [...FONT_FAMILIES] : [value, ...FONT_FAMILIES];
1417
889
  const filtered = families.filter((f) => f.toLowerCase().includes(query.trim().toLowerCase()));
1418
890
  return /* @__PURE__ */ jsxs5("label", { className: "flex flex-col items-center gap-0.5", children: [
1419
- /* @__PURE__ */ jsx5("span", { className: FIELD_LABEL, children: "Font" }),
1420
- /* @__PURE__ */ jsx5(
891
+ /* @__PURE__ */ jsx6("span", { className: FIELD_LABEL, children: "Font" }),
892
+ /* @__PURE__ */ jsx6(
1421
893
  Popover,
1422
894
  {
1423
895
  open,
@@ -1437,13 +909,13 @@ function FontPicker({
1437
909
  className: "flex w-28 items-center justify-between gap-1 rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-2 py-0.5 text-left text-[12px] text-[var(--text-primary)] outline-none hover:border-[var(--brand-primary)] disabled:cursor-default disabled:opacity-40",
1438
910
  style: { fontFamily: value },
1439
911
  children: [
1440
- /* @__PURE__ */ jsx5("span", { className: "truncate", children: value }),
1441
- /* @__PURE__ */ jsx5("span", { className: "text-[8px] text-[var(--text-muted)]", children: "\u25BE" })
912
+ /* @__PURE__ */ jsx6("span", { className: "truncate", children: value }),
913
+ /* @__PURE__ */ jsx6("span", { className: "text-[8px] text-[var(--text-muted)]", children: "\u25BE" })
1442
914
  ]
1443
915
  }
1444
916
  ),
1445
917
  children: /* @__PURE__ */ jsxs5("div", { className: `${POPOVER_PANEL} w-52`, children: [
1446
- /* @__PURE__ */ jsx5(
918
+ /* @__PURE__ */ jsx6(
1447
919
  "input",
1448
920
  {
1449
921
  autoFocus: true,
@@ -1454,7 +926,7 @@ function FontPicker({
1454
926
  className: "m-1 rounded border border-[var(--border-default)] bg-transparent px-2 py-1 text-[12px] text-[var(--text-primary)] outline-none focus:border-[var(--brand-primary)]"
1455
927
  }
1456
928
  ),
1457
- /* @__PURE__ */ jsx5("div", { role: "listbox", className: "max-h-60 overflow-y-auto py-1", children: filtered.length === 0 ? /* @__PURE__ */ jsx5("div", { className: "px-3 py-2 text-[11px] text-[var(--text-muted)]", children: "No matches" }) : filtered.map((family) => /* @__PURE__ */ jsx5(
929
+ /* @__PURE__ */ jsx6("div", { role: "listbox", className: "max-h-60 overflow-y-auto py-1", children: filtered.length === 0 ? /* @__PURE__ */ jsx6("div", { className: "px-3 py-2 text-[11px] text-[var(--text-muted)]", children: "No matches" }) : filtered.map((family) => /* @__PURE__ */ jsx6(
1458
930
  "button",
1459
931
  {
1460
932
  type: "button",
@@ -1480,13 +952,13 @@ function ColorSwatch({ label, value, onCommit, disabled }) {
1480
952
  const [open, setOpen] = useState3(false);
1481
953
  const normalized = value.startsWith("#") ? value : "#ffffff";
1482
954
  return /* @__PURE__ */ jsxs5("div", { className: "flex flex-col items-center gap-0.5", children: [
1483
- /* @__PURE__ */ jsx5("span", { className: FIELD_LABEL, children: label }),
1484
- /* @__PURE__ */ jsx5(
955
+ /* @__PURE__ */ jsx6("span", { className: FIELD_LABEL, children: label }),
956
+ /* @__PURE__ */ jsx6(
1485
957
  Popover,
1486
958
  {
1487
959
  open,
1488
960
  onClose: () => setOpen(false),
1489
- trigger: /* @__PURE__ */ jsx5(
961
+ trigger: /* @__PURE__ */ jsx6(
1490
962
  "button",
1491
963
  {
1492
964
  type: "button",
@@ -1498,7 +970,7 @@ function ColorSwatch({ label, value, onCommit, disabled }) {
1498
970
  }
1499
971
  ),
1500
972
  children: /* @__PURE__ */ jsxs5("div", { className: `${POPOVER_PANEL} w-40 gap-2 p-2`, children: [
1501
- /* @__PURE__ */ jsx5(
973
+ /* @__PURE__ */ jsx6(
1502
974
  "input",
1503
975
  {
1504
976
  type: "color",
@@ -1508,7 +980,7 @@ function ColorSwatch({ label, value, onCommit, disabled }) {
1508
980
  className: "h-8 w-full cursor-pointer rounded border border-[var(--border-default)] p-0.5"
1509
981
  }
1510
982
  ),
1511
- /* @__PURE__ */ jsx5(
983
+ /* @__PURE__ */ jsx6(
1512
984
  "input",
1513
985
  {
1514
986
  type: "text",
@@ -1527,6 +999,7 @@ function Toolbar({
1527
999
  page,
1528
1000
  selectedElements,
1529
1001
  canWrite,
1002
+ mode = "edit",
1530
1003
  canUndo,
1531
1004
  canRedo,
1532
1005
  gridEnabled,
@@ -1562,46 +1035,55 @@ function Toolbar({
1562
1035
  const selectedIds = selectedElements.map((e) => e.id);
1563
1036
  const isGroup = single?.kind === "group";
1564
1037
  const groupable = selectedElements.length >= 2;
1038
+ const review = mode === "review";
1565
1039
  return /* @__PURE__ */ jsxs5("div", { className: "flex min-h-11 shrink-0 flex-wrap items-center gap-x-2 gap-y-1 border-b border-[var(--border-default)] bg-[var(--bg-input)] px-3 py-1", children: [
1566
1040
  /* @__PURE__ */ jsxs5("div", { className: "flex shrink-0 items-center gap-2", children: [
1567
- /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Undo", disabled: !canUndo || !canWrite, onClick: onUndo, className: BTN2, children: /* @__PURE__ */ jsx5(UndoGlyph, { className: "h-3.5 w-3.5" }) }),
1568
- /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Redo", disabled: !canRedo || !canWrite, onClick: onRedo, className: BTN2, children: /* @__PURE__ */ jsx5(RedoGlyph, { className: "h-3.5 w-3.5" }) }),
1569
- SEP,
1570
- /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Toggle rulers", "aria-pressed": showRulers, onClick: onToggleRulers, className: showRulers ? BTN_ACTIVE : BTN2, children: /* @__PURE__ */ jsx5(RulerGlyph, { className: "h-3.5 w-3.5" }) }),
1571
- /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Toggle grid", "aria-pressed": gridEnabled, onClick: onToggleGrid, className: gridEnabled ? BTN_ACTIVE : BTN2, children: /* @__PURE__ */ jsx5(GridGlyph, { className: "h-3.5 w-3.5" }) }),
1572
- /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Toggle snap", "aria-pressed": snapEnabled, onClick: onToggleSnap, className: snapEnabled ? BTN_ACTIVE : BTN2, children: /* @__PURE__ */ jsx5(MagnetGlyph, { className: "h-3.5 w-3.5" }) }),
1573
- /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Toggle bleed overlay", "aria-pressed": showBleed, onClick: onToggleBleed, className: showBleed ? BTN_ACTIVE : BTN2, disabled: !page.bleed, children: /* @__PURE__ */ jsx5(BleedGlyph, { className: "h-3.5 w-3.5" }) })
1041
+ /* @__PURE__ */ jsx6("button", { type: "button", "aria-label": "Undo", disabled: !canUndo || !canWrite, onClick: onUndo, className: BTN, children: /* @__PURE__ */ jsx6(UndoGlyph, { className: "h-3.5 w-3.5" }) }),
1042
+ /* @__PURE__ */ jsx6("button", { type: "button", "aria-label": "Redo", disabled: !canRedo || !canWrite, onClick: onRedo, className: BTN, children: /* @__PURE__ */ jsx6(RedoGlyph, { className: "h-3.5 w-3.5" }) }),
1043
+ !review ? /* @__PURE__ */ jsxs5(Fragment4, { children: [
1044
+ SEP,
1045
+ /* @__PURE__ */ jsx6("button", { type: "button", "aria-label": "Toggle rulers", "aria-pressed": showRulers, onClick: onToggleRulers, className: showRulers ? BTN_ACTIVE : BTN, children: /* @__PURE__ */ jsx6(RulerGlyph, { className: "h-3.5 w-3.5" }) }),
1046
+ /* @__PURE__ */ jsx6("button", { type: "button", "aria-label": "Toggle grid", "aria-pressed": gridEnabled, onClick: onToggleGrid, className: gridEnabled ? BTN_ACTIVE : BTN, children: /* @__PURE__ */ jsx6(GridGlyph, { className: "h-3.5 w-3.5" }) }),
1047
+ /* @__PURE__ */ jsx6("button", { type: "button", "aria-label": "Toggle snap", "aria-pressed": snapEnabled, onClick: onToggleSnap, className: snapEnabled ? BTN_ACTIVE : BTN, children: /* @__PURE__ */ jsx6(MagnetGlyph, { className: "h-3.5 w-3.5" }) }),
1048
+ /* @__PURE__ */ jsx6("button", { type: "button", "aria-label": "Toggle bleed overlay", "aria-pressed": showBleed, onClick: onToggleBleed, className: showBleed ? BTN_ACTIVE : BTN, disabled: !page.bleed, children: /* @__PURE__ */ jsx6(BleedGlyph, { className: "h-3.5 w-3.5" }) })
1049
+ ] }) : null
1574
1050
  ] }),
1575
- SEP,
1576
- /* @__PURE__ */ jsx5("div", { className: "flex flex-wrap items-center gap-x-2 gap-y-1", children: hasSelection ? /* @__PURE__ */ jsx5(
1577
- SelectionControls,
1578
- {
1579
- elements: selectedElements,
1580
- single,
1581
- isGroup,
1582
- groupable,
1583
- allSameKind,
1584
- firstKind,
1585
- canWrite,
1586
- patchAll,
1587
- reorderSingle,
1588
- onGroup: () => onGroup(selectedIds),
1589
- onUngroup: () => {
1590
- if (single) onUngroup(single.id);
1591
- },
1592
- onDelete: () => onDelete(selectedIds),
1593
- onBindSlot: single ? (slot) => onBindSlot(single.id, slot) : void 0,
1594
- currentSlot: single?.slot ?? null
1595
- }
1596
- ) : /* @__PURE__ */ jsx5(
1597
- PagePropsControls,
1598
- {
1599
- page,
1600
- canWrite,
1601
- onSetPageProps,
1602
- onSetPageGuides
1603
- }
1604
- ) })
1051
+ /* @__PURE__ */ jsx6("div", { className: "flex flex-wrap items-center gap-x-2 gap-y-1", children: hasSelection ? /* @__PURE__ */ jsxs5(Fragment4, { children: [
1052
+ SEP,
1053
+ /* @__PURE__ */ jsx6(
1054
+ SelectionControls,
1055
+ {
1056
+ elements: selectedElements,
1057
+ single,
1058
+ isGroup,
1059
+ groupable,
1060
+ allSameKind,
1061
+ firstKind,
1062
+ canWrite,
1063
+ review,
1064
+ patchAll,
1065
+ reorderSingle,
1066
+ onGroup: () => onGroup(selectedIds),
1067
+ onUngroup: () => {
1068
+ if (single) onUngroup(single.id);
1069
+ },
1070
+ onDelete: () => onDelete(selectedIds),
1071
+ onBindSlot: single ? (slot) => onBindSlot(single.id, slot) : void 0,
1072
+ currentSlot: single?.slot ?? null
1073
+ }
1074
+ )
1075
+ ] }) : !review ? /* @__PURE__ */ jsxs5(Fragment4, { children: [
1076
+ SEP,
1077
+ /* @__PURE__ */ jsx6(
1078
+ PagePropsControls,
1079
+ {
1080
+ page,
1081
+ canWrite,
1082
+ onSetPageProps,
1083
+ onSetPageGuides
1084
+ }
1085
+ )
1086
+ ] }) : null })
1605
1087
  ] });
1606
1088
  }
1607
1089
  function SelectionControls({
@@ -1612,6 +1094,7 @@ function SelectionControls({
1612
1094
  allSameKind,
1613
1095
  firstKind,
1614
1096
  canWrite,
1097
+ review,
1615
1098
  patchAll,
1616
1099
  reorderSingle,
1617
1100
  onGroup,
@@ -1624,12 +1107,12 @@ function SelectionControls({
1624
1107
  const [slotInput, setSlotInput] = useState3("");
1625
1108
  const firstEl = elements[0];
1626
1109
  return /* @__PURE__ */ jsxs5(Fragment4, { children: [
1627
- allSameKind && firstKind === "text" && single ? /* @__PURE__ */ jsx5(TextControls, { element: single, canWrite, onPatch: (attrs) => patchAll(attrs) }) : null,
1628
- allSameKind && firstKind === "rect" && single ? /* @__PURE__ */ jsx5(ShapeControls, { element: single, canWrite, onPatch: (attrs) => patchAll(attrs), showCornerRadius: true }) : null,
1629
- allSameKind && firstKind === "ellipse" && single ? /* @__PURE__ */ jsx5(ShapeControls, { element: single, canWrite, onPatch: (attrs) => patchAll(attrs), showCornerRadius: false }) : null,
1630
- allSameKind && firstKind === "image" && single ? /* @__PURE__ */ jsx5(ImageControls, { element: single, canWrite, onPatch: (attrs) => patchAll(attrs) }) : null,
1110
+ allSameKind && firstKind === "text" && single ? /* @__PURE__ */ jsx6(TextControls, { element: single, canWrite, onPatch: (attrs) => patchAll(attrs) }) : null,
1111
+ allSameKind && firstKind === "rect" && single ? /* @__PURE__ */ jsx6(ShapeControls, { element: single, canWrite, onPatch: (attrs) => patchAll(attrs), showCornerRadius: true }) : null,
1112
+ allSameKind && firstKind === "ellipse" && single ? /* @__PURE__ */ jsx6(ShapeControls, { element: single, canWrite, onPatch: (attrs) => patchAll(attrs), showCornerRadius: false }) : null,
1113
+ allSameKind && firstKind === "image" && single ? /* @__PURE__ */ jsx6(ImageControls, { element: single, canWrite, onPatch: (attrs) => patchAll(attrs) }) : null,
1631
1114
  SEP,
1632
- /* @__PURE__ */ jsx5(
1115
+ /* @__PURE__ */ jsx6(
1633
1116
  NumberInput,
1634
1117
  {
1635
1118
  label: "Opacity",
@@ -1639,7 +1122,7 @@ function SelectionControls({
1639
1122
  className: "w-14"
1640
1123
  }
1641
1124
  ),
1642
- /* @__PURE__ */ jsx5(
1125
+ /* @__PURE__ */ jsx6(
1643
1126
  NumberInput,
1644
1127
  {
1645
1128
  label: "Rotation",
@@ -1648,152 +1131,225 @@ function SelectionControls({
1648
1131
  className: "w-14"
1649
1132
  }
1650
1133
  ),
1651
- SEP,
1652
- single ? /* @__PURE__ */ jsxs5(Fragment4, { children: [
1653
- /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Bring to front", disabled: !canWrite, onClick: () => reorderSingle("front"), className: BTN2, children: /* @__PURE__ */ jsx5(BringFrontGlyph, { className: "h-3.5 w-3.5" }) }),
1654
- /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Send to back", disabled: !canWrite, onClick: () => reorderSingle("back"), className: BTN2, children: /* @__PURE__ */ jsx5(SendBackGlyph, { className: "h-3.5 w-3.5" }) }),
1655
- SEP
1656
- ] }) : null,
1657
- groupable ? /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Group elements", disabled: !canWrite, onClick: onGroup, className: BTN2, children: /* @__PURE__ */ jsx5(GroupGlyph, { className: "h-3.5 w-3.5" }) }) : null,
1658
- isGroup ? /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Ungroup", disabled: !canWrite, onClick: onUngroup, className: BTN2, children: /* @__PURE__ */ jsx5(UngroupGlyph, { className: "h-3.5 w-3.5" }) }) : null,
1659
- single ? /* @__PURE__ */ jsx5(
1660
- "button",
1661
- {
1662
- type: "button",
1663
- "aria-label": single.locked ? "Unlock element" : "Lock element",
1664
- disabled: !canWrite,
1665
- onClick: () => patchAll({ locked: !single.locked }),
1666
- className: single.locked ? BTN_ACTIVE : BTN2,
1667
- children: single.locked ? /* @__PURE__ */ jsx5(LockGlyph, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx5(UnlockGlyph, { className: "h-3.5 w-3.5" })
1668
- }
1669
- ) : null,
1670
- single && onBindSlot ? /* @__PURE__ */ jsx5(
1671
- Popover,
1672
- {
1673
- open: slotPopoverOpen,
1674
- onClose: () => setSlotPopoverOpen(false),
1675
- trigger: /* @__PURE__ */ jsx5(
1676
- "button",
1677
- {
1678
- type: "button",
1679
- "aria-label": currentSlot ? `Slot: ${currentSlot}` : "Bind slot",
1680
- onClick: () => {
1681
- setSlotInput(currentSlot ?? "");
1682
- setSlotPopoverOpen((v) => !v);
1683
- },
1684
- className: currentSlot ? BTN_ACTIVE : BTN2,
1685
- title: currentSlot ? `Slot: ${currentSlot}` : "Bind slot",
1686
- children: /* @__PURE__ */ jsx5(SlotGlyph, { className: "h-3.5 w-3.5" })
1687
- }
1688
- ),
1689
- children: /* @__PURE__ */ jsxs5("div", { className: `${POPOVER_PANEL} w-48 gap-2 p-2`, children: [
1690
- /* @__PURE__ */ jsx5(
1691
- "input",
1134
+ review ? null : /* @__PURE__ */ jsxs5(Fragment4, { children: [
1135
+ SEP,
1136
+ single ? /* @__PURE__ */ jsxs5(Fragment4, { children: [
1137
+ /* @__PURE__ */ jsx6("button", { type: "button", "aria-label": "Bring to front", disabled: !canWrite, onClick: () => reorderSingle("front"), className: BTN, children: /* @__PURE__ */ jsx6(BringFrontGlyph, { className: "h-3.5 w-3.5" }) }),
1138
+ /* @__PURE__ */ jsx6("button", { type: "button", "aria-label": "Send to back", disabled: !canWrite, onClick: () => reorderSingle("back"), className: BTN, children: /* @__PURE__ */ jsx6(SendBackGlyph, { className: "h-3.5 w-3.5" }) }),
1139
+ SEP
1140
+ ] }) : null,
1141
+ groupable ? /* @__PURE__ */ jsx6("button", { type: "button", "aria-label": "Group elements", disabled: !canWrite, onClick: onGroup, className: BTN, children: /* @__PURE__ */ jsx6(GroupGlyph, { className: "h-3.5 w-3.5" }) }) : null,
1142
+ isGroup ? /* @__PURE__ */ jsx6("button", { type: "button", "aria-label": "Ungroup", disabled: !canWrite, onClick: onUngroup, className: BTN, children: /* @__PURE__ */ jsx6(UngroupGlyph, { className: "h-3.5 w-3.5" }) }) : null,
1143
+ single ? /* @__PURE__ */ jsx6(
1144
+ "button",
1145
+ {
1146
+ type: "button",
1147
+ "aria-label": single.locked ? "Unlock element" : "Lock element",
1148
+ "aria-pressed": !!single.locked,
1149
+ disabled: !canWrite,
1150
+ onClick: () => patchAll({ locked: !single.locked }),
1151
+ className: single.locked ? BTN_ACTIVE : BTN,
1152
+ children: single.locked ? /* @__PURE__ */ jsx6(LockGlyph, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx6(UnlockGlyph, { className: "h-3.5 w-3.5" })
1153
+ }
1154
+ ) : null,
1155
+ single && onBindSlot ? /* @__PURE__ */ jsx6(
1156
+ Popover,
1157
+ {
1158
+ open: slotPopoverOpen,
1159
+ onClose: () => setSlotPopoverOpen(false),
1160
+ trigger: /* @__PURE__ */ jsx6(
1161
+ "button",
1692
1162
  {
1693
- autoFocus: true,
1694
- value: slotInput,
1695
- onChange: (event) => setSlotInput(event.target.value),
1696
- placeholder: "slot-name",
1697
- className: "rounded border border-[var(--border-default)] bg-transparent px-2 py-1 text-[12px] text-[var(--text-primary)] outline-none focus:border-[var(--brand-primary)]"
1163
+ type: "button",
1164
+ "aria-label": currentSlot ? `Slot: ${currentSlot}` : "Bind slot",
1165
+ "aria-pressed": !!currentSlot,
1166
+ "aria-haspopup": "dialog",
1167
+ "aria-expanded": slotPopoverOpen,
1168
+ onClick: () => {
1169
+ setSlotInput(currentSlot ?? "");
1170
+ setSlotPopoverOpen((v) => !v);
1171
+ },
1172
+ className: currentSlot ? BTN_ACTIVE : BTN,
1173
+ title: currentSlot ? `Slot: ${currentSlot}` : "Bind slot",
1174
+ children: /* @__PURE__ */ jsx6(SlotGlyph, { className: "h-3.5 w-3.5" })
1698
1175
  }
1699
1176
  ),
1700
- /* @__PURE__ */ jsxs5("div", { className: "flex gap-2", children: [
1701
- /* @__PURE__ */ jsx5(
1702
- "button",
1177
+ children: /* @__PURE__ */ jsxs5("div", { className: `${POPOVER_PANEL} w-48 gap-2 p-2`, children: [
1178
+ /* @__PURE__ */ jsx6(
1179
+ "input",
1703
1180
  {
1704
- type: "button",
1705
- onClick: () => {
1706
- onBindSlot(slotInput.trim() || null);
1707
- setSlotPopoverOpen(false);
1708
- },
1709
- className: "flex-1 rounded border border-[var(--brand-primary)] px-2 py-0.5 text-[11px] text-[var(--brand-primary)] hover:bg-[var(--brand-primary)]/10",
1710
- children: slotInput.trim() ? "Bind" : "Unbind"
1181
+ autoFocus: true,
1182
+ value: slotInput,
1183
+ onChange: (event) => setSlotInput(event.target.value),
1184
+ placeholder: "slot-name",
1185
+ className: "rounded border border-[var(--border-default)] bg-transparent px-2 py-1 text-[12px] text-[var(--text-primary)] outline-none focus:border-[var(--brand-primary)]"
1711
1186
  }
1712
1187
  ),
1713
- /* @__PURE__ */ jsx5(
1714
- "button",
1715
- {
1716
- type: "button",
1717
- onClick: () => setSlotPopoverOpen(false),
1718
- className: "rounded border border-[var(--border-default)] px-2 py-0.5 text-[11px] text-[var(--text-secondary)]",
1719
- children: "Cancel"
1720
- }
1721
- )
1188
+ /* @__PURE__ */ jsxs5("div", { className: "flex gap-2", children: [
1189
+ /* @__PURE__ */ jsx6(
1190
+ "button",
1191
+ {
1192
+ type: "button",
1193
+ onClick: () => {
1194
+ onBindSlot(slotInput.trim() || null);
1195
+ setSlotPopoverOpen(false);
1196
+ },
1197
+ className: "flex-1 rounded border border-[var(--brand-primary)] px-2 py-0.5 text-[11px] text-[var(--brand-primary)] hover:bg-[var(--brand-primary)]/10",
1198
+ children: slotInput.trim() ? "Bind" : "Unbind"
1199
+ }
1200
+ ),
1201
+ /* @__PURE__ */ jsx6(
1202
+ "button",
1203
+ {
1204
+ type: "button",
1205
+ onClick: () => setSlotPopoverOpen(false),
1206
+ className: "rounded border border-[var(--border-default)] px-2 py-0.5 text-[11px] text-[var(--text-secondary)]",
1207
+ children: "Cancel"
1208
+ }
1209
+ )
1210
+ ] })
1722
1211
  ] })
1723
- ] })
1724
- }
1725
- ) : null,
1726
- SEP,
1727
- /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Delete selection", disabled: !canWrite, onClick: onDelete, className: BTN2, children: /* @__PURE__ */ jsx5(TrashGlyph, { className: "h-3.5 w-3.5 text-rose-400" }) })
1212
+ }
1213
+ ) : null,
1214
+ SEP,
1215
+ /* @__PURE__ */ jsx6("button", { type: "button", "aria-label": "Delete selection", disabled: !canWrite, onClick: onDelete, className: BTN, children: /* @__PURE__ */ jsx6(TrashGlyph, { className: "h-3.5 w-3.5 text-[var(--text-danger)]" }) })
1216
+ ] })
1728
1217
  ] });
1729
1218
  }
1730
1219
  function TextControls({ element, canWrite, onPatch }) {
1731
1220
  return /* @__PURE__ */ jsxs5(Fragment4, { children: [
1732
- /* @__PURE__ */ jsx5(FontPicker, { value: element.fontFamily, disabled: !canWrite, onChange: (fontFamily) => onPatch({ fontFamily }) }),
1733
- /* @__PURE__ */ jsx5(NumberInput, { label: "Size", value: element.fontSize, min: 1, onCommit: (v) => onPatch({ fontSize: v }), className: "w-12" }),
1734
- /* @__PURE__ */ jsx5(
1221
+ /* @__PURE__ */ jsx6(FontPicker, { value: element.fontFamily, disabled: !canWrite, onChange: (fontFamily) => onPatch({ fontFamily }) }),
1222
+ /* @__PURE__ */ jsx6(NumberInput, { label: "Size", value: element.fontSize, min: 1, onCommit: (v) => onPatch({ fontSize: v }), className: "w-12" }),
1223
+ /* @__PURE__ */ jsx6(
1735
1224
  "button",
1736
1225
  {
1737
1226
  type: "button",
1738
1227
  "aria-label": "Bold",
1228
+ "aria-pressed": !!element.fontStyle?.includes("bold"),
1739
1229
  disabled: !canWrite,
1740
1230
  onClick: () => onPatch({ fontStyle: element.fontStyle === "bold" || element.fontStyle === "bold italic" ? element.fontStyle === "bold italic" ? "italic" : "normal" : element.fontStyle === "italic" ? "bold italic" : "bold" }),
1741
- className: element.fontStyle?.includes("bold") ? BTN_ACTIVE : BTN2,
1742
- children: /* @__PURE__ */ jsx5(BoldGlyph, { className: "h-3.5 w-3.5" })
1231
+ className: element.fontStyle?.includes("bold") ? BTN_ACTIVE : BTN,
1232
+ children: /* @__PURE__ */ jsx6(BoldGlyph, { className: "h-3.5 w-3.5" })
1743
1233
  }
1744
1234
  ),
1745
- /* @__PURE__ */ jsx5(
1235
+ /* @__PURE__ */ jsx6(
1746
1236
  "button",
1747
1237
  {
1748
1238
  type: "button",
1749
1239
  "aria-label": "Italic",
1240
+ "aria-pressed": !!element.fontStyle?.includes("italic"),
1750
1241
  disabled: !canWrite,
1751
1242
  onClick: () => onPatch({ fontStyle: element.fontStyle === "italic" || element.fontStyle === "bold italic" ? element.fontStyle === "bold italic" ? "bold" : "normal" : element.fontStyle === "bold" ? "bold italic" : "italic" }),
1752
- className: element.fontStyle?.includes("italic") ? BTN_ACTIVE : BTN2,
1753
- children: /* @__PURE__ */ jsx5(ItalicGlyph, { className: "h-3.5 w-3.5" })
1243
+ className: element.fontStyle?.includes("italic") ? BTN_ACTIVE : BTN,
1244
+ children: /* @__PURE__ */ jsx6(ItalicGlyph, { className: "h-3.5 w-3.5" })
1754
1245
  }
1755
1246
  ),
1756
- ["left", "center", "right"].map((align) => /* @__PURE__ */ jsx5(
1247
+ /* @__PURE__ */ jsx6("div", { role: "radiogroup", "aria-label": "Text alignment", className: "flex items-center gap-2", children: ["left", "center", "right"].map((align) => /* @__PURE__ */ jsx6(
1757
1248
  "button",
1758
1249
  {
1759
1250
  type: "button",
1251
+ role: "radio",
1760
1252
  "aria-label": `Align ${align}`,
1253
+ "aria-checked": element.align === align,
1761
1254
  disabled: !canWrite,
1762
1255
  onClick: () => onPatch({ align }),
1763
- className: element.align === align ? BTN_ACTIVE : BTN2,
1764
- children: align === "left" ? /* @__PURE__ */ jsx5(AlignLeftGlyph, { className: "h-3.5 w-3.5" }) : align === "center" ? /* @__PURE__ */ jsx5(AlignCenterGlyph, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx5(AlignRightGlyph, { className: "h-3.5 w-3.5" })
1256
+ className: element.align === align ? BTN_ACTIVE : BTN,
1257
+ children: align === "left" ? /* @__PURE__ */ jsx6(AlignLeftGlyph, { className: "h-3.5 w-3.5" }) : align === "center" ? /* @__PURE__ */ jsx6(AlignCenterGlyph, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx6(AlignRightGlyph, { className: "h-3.5 w-3.5" })
1765
1258
  },
1766
1259
  align
1767
- )),
1768
- /* @__PURE__ */ jsx5(NumberInput, { label: "Line H", value: element.lineHeight, step: 0.1, min: 0.5, onCommit: (v) => onPatch({ lineHeight: v }), className: "w-12" }),
1769
- /* @__PURE__ */ jsx5(NumberInput, { label: "Spacing", value: element.letterSpacing, step: 0.5, onCommit: (v) => onPatch({ letterSpacing: v }), className: "w-14" }),
1770
- /* @__PURE__ */ jsx5(ColorSwatch, { label: "Fill", value: element.fill, onCommit: (v) => onPatch({ fill: v }), disabled: !canWrite })
1260
+ )) }),
1261
+ /* @__PURE__ */ jsx6(NumberInput, { label: "Line H", value: element.lineHeight, step: 0.1, min: 0.5, onCommit: (v) => onPatch({ lineHeight: v }), className: "w-12" }),
1262
+ /* @__PURE__ */ jsx6(NumberInput, { label: "Spacing", value: element.letterSpacing, step: 0.5, onCommit: (v) => onPatch({ letterSpacing: v }), className: "w-14" }),
1263
+ /* @__PURE__ */ jsx6(ColorSwatch, { label: "Fill", value: element.fill, onCommit: (v) => onPatch({ fill: v }), disabled: !canWrite })
1771
1264
  ] });
1772
1265
  }
1773
1266
  function ShapeControls({ element, canWrite, onPatch, showCornerRadius }) {
1774
1267
  return /* @__PURE__ */ jsxs5(Fragment4, { children: [
1775
- /* @__PURE__ */ jsx5(ColorSwatch, { label: "Fill", value: element.fill, onCommit: (v) => onPatch({ fill: v }), disabled: !canWrite }),
1776
- /* @__PURE__ */ jsx5(ColorSwatch, { label: "Stroke", value: element.stroke ?? "#000000", onCommit: (v) => onPatch({ stroke: v }), disabled: !canWrite }),
1777
- /* @__PURE__ */ jsx5(NumberInput, { label: "Stroke W", value: element.strokeWidth ?? 0, min: 0, onCommit: (v) => onPatch({ strokeWidth: v }), className: "w-14" }),
1778
- showCornerRadius && "cornerRadius" in element ? /* @__PURE__ */ jsx5(NumberInput, { label: "Corner R", value: element.cornerRadius ?? 0, min: 0, onCommit: (v) => onPatch({ cornerRadius: v }), className: "w-14" }) : null
1268
+ /* @__PURE__ */ jsx6(ColorSwatch, { label: "Fill", value: element.fill, onCommit: (v) => onPatch({ fill: v }), disabled: !canWrite }),
1269
+ /* @__PURE__ */ jsx6(ColorSwatch, { label: "Stroke", value: element.stroke ?? "#000000", onCommit: (v) => onPatch({ stroke: v }), disabled: !canWrite }),
1270
+ /* @__PURE__ */ jsx6(NumberInput, { label: "Stroke W", value: element.strokeWidth ?? 0, min: 0, onCommit: (v) => onPatch({ strokeWidth: v }), className: "w-14" }),
1271
+ showCornerRadius && "cornerRadius" in element ? /* @__PURE__ */ jsx6(NumberInput, { label: "Corner R", value: element.cornerRadius ?? 0, min: 0, onCommit: (v) => onPatch({ cornerRadius: v }), className: "w-14" }) : null
1779
1272
  ] });
1780
1273
  }
1781
1274
  function ImageControls({ element, canWrite, onPatch }) {
1782
- return /* @__PURE__ */ jsx5(
1783
- SelectControl,
1784
- {
1785
- label: "Fit",
1786
- value: element.fit,
1787
- disabled: !canWrite,
1788
- onChange: (fit) => onPatch({ fit }),
1789
- buttonClassName: "w-24",
1790
- options: [
1791
- { value: "fill", label: "Fill" },
1792
- { value: "cover", label: "Cover" },
1793
- { value: "contain", label: "Contain" }
1794
- ]
1795
- }
1796
- );
1275
+ const [swapOpen, setSwapOpen] = useState3(false);
1276
+ const [swapUrl, setSwapUrl] = useState3("");
1277
+ return /* @__PURE__ */ jsxs5(Fragment4, { children: [
1278
+ /* @__PURE__ */ jsx6(
1279
+ SelectControl,
1280
+ {
1281
+ label: "Fit",
1282
+ value: element.fit,
1283
+ disabled: !canWrite,
1284
+ onChange: (fit) => onPatch({ fit }),
1285
+ buttonClassName: "w-24",
1286
+ options: [
1287
+ { value: "fill", label: "Fill" },
1288
+ { value: "cover", label: "Cover" },
1289
+ { value: "contain", label: "Contain" }
1290
+ ]
1291
+ }
1292
+ ),
1293
+ /* @__PURE__ */ jsx6(
1294
+ Popover,
1295
+ {
1296
+ open: swapOpen,
1297
+ onClose: () => setSwapOpen(false),
1298
+ trigger: /* @__PURE__ */ jsx6(
1299
+ "button",
1300
+ {
1301
+ type: "button",
1302
+ "aria-label": "Replace image",
1303
+ disabled: !canWrite,
1304
+ onClick: () => {
1305
+ setSwapUrl(element.src);
1306
+ setSwapOpen((v) => !v);
1307
+ },
1308
+ className: BTN,
1309
+ title: "Replace image",
1310
+ children: /* @__PURE__ */ jsx6(SwapGlyph, { className: "h-3.5 w-3.5" })
1311
+ }
1312
+ ),
1313
+ children: /* @__PURE__ */ jsxs5("div", { className: `${POPOVER_PANEL} w-64 gap-2 p-2`, children: [
1314
+ /* @__PURE__ */ jsx6(
1315
+ "input",
1316
+ {
1317
+ autoFocus: true,
1318
+ value: swapUrl,
1319
+ onChange: (event) => setSwapUrl(event.target.value),
1320
+ placeholder: "https://\u2026 image URL",
1321
+ "aria-label": "New image URL",
1322
+ className: "rounded border border-[var(--border-default)] bg-transparent px-2 py-1 text-[12px] text-[var(--text-primary)] outline-none focus:border-[var(--brand-primary)]"
1323
+ }
1324
+ ),
1325
+ /* @__PURE__ */ jsxs5("div", { className: "flex gap-2", children: [
1326
+ /* @__PURE__ */ jsx6(
1327
+ "button",
1328
+ {
1329
+ type: "button",
1330
+ disabled: !swapUrl.trim() || swapUrl.trim() === element.src,
1331
+ onClick: () => {
1332
+ onPatch({ src: swapUrl.trim() });
1333
+ setSwapOpen(false);
1334
+ },
1335
+ className: "flex-1 rounded border border-[var(--brand-primary)] px-2 py-0.5 text-[11px] text-[var(--brand-primary)] hover:bg-[var(--brand-primary)]/10 disabled:cursor-default disabled:opacity-40",
1336
+ children: "Replace"
1337
+ }
1338
+ ),
1339
+ /* @__PURE__ */ jsx6(
1340
+ "button",
1341
+ {
1342
+ type: "button",
1343
+ onClick: () => setSwapOpen(false),
1344
+ className: "rounded border border-[var(--border-default)] px-2 py-0.5 text-[11px] text-[var(--text-secondary)]",
1345
+ children: "Cancel"
1346
+ }
1347
+ )
1348
+ ] })
1349
+ ] })
1350
+ }
1351
+ )
1352
+ ] });
1797
1353
  }
1798
1354
  function PagePropsControls({ page, canWrite, onSetPageProps, onSetPageGuides }) {
1799
1355
  const matchedPreset = matchPreset(page.width, page.height);
@@ -1810,7 +1366,7 @@ function PagePropsControls({ page, canWrite, onSetPageProps, onSetPageGuides })
1810
1366
  ...SIZE_PRESETS.map((p) => ({ value: p.id, label: p.label }))
1811
1367
  ];
1812
1368
  return /* @__PURE__ */ jsxs5(Fragment4, { children: [
1813
- /* @__PURE__ */ jsx5(
1369
+ /* @__PURE__ */ jsx6(
1814
1370
  "input",
1815
1371
  {
1816
1372
  type: "text",
@@ -1822,7 +1378,7 @@ function PagePropsControls({ page, canWrite, onSetPageProps, onSetPageGuides })
1822
1378
  }
1823
1379
  ),
1824
1380
  SEP,
1825
- /* @__PURE__ */ jsx5(
1381
+ /* @__PURE__ */ jsx6(
1826
1382
  SelectControl,
1827
1383
  {
1828
1384
  label: "Preset",
@@ -1836,8 +1392,8 @@ function PagePropsControls({ page, canWrite, onSetPageProps, onSetPageGuides })
1836
1392
  }
1837
1393
  ),
1838
1394
  /* @__PURE__ */ jsxs5("label", { className: "flex flex-col items-center gap-0.5", children: [
1839
- /* @__PURE__ */ jsx5("span", { className: FIELD_LABEL, children: "W" }),
1840
- /* @__PURE__ */ jsx5(
1395
+ /* @__PURE__ */ jsx6("span", { className: FIELD_LABEL, children: "W" }),
1396
+ /* @__PURE__ */ jsx6(
1841
1397
  "input",
1842
1398
  {
1843
1399
  type: "number",
@@ -1854,10 +1410,10 @@ function PagePropsControls({ page, canWrite, onSetPageProps, onSetPageGuides })
1854
1410
  }
1855
1411
  )
1856
1412
  ] }),
1857
- /* @__PURE__ */ jsx5("span", { className: "text-[var(--text-muted)]", children: "\xD7" }),
1413
+ /* @__PURE__ */ jsx6("span", { className: "text-[var(--text-muted)]", children: "\xD7" }),
1858
1414
  /* @__PURE__ */ jsxs5("label", { className: "flex flex-col items-center gap-0.5", children: [
1859
- /* @__PURE__ */ jsx5("span", { className: FIELD_LABEL, children: "H" }),
1860
- /* @__PURE__ */ jsx5(
1415
+ /* @__PURE__ */ jsx6("span", { className: FIELD_LABEL, children: "H" }),
1416
+ /* @__PURE__ */ jsx6(
1861
1417
  "input",
1862
1418
  {
1863
1419
  type: "number",
@@ -1875,9 +1431,9 @@ function PagePropsControls({ page, canWrite, onSetPageProps, onSetPageGuides })
1875
1431
  )
1876
1432
  ] }),
1877
1433
  SEP,
1878
- /* @__PURE__ */ jsx5(ColorSwatch, { label: "BG", value: page.background, onCommit: (v) => onSetPageProps({ background: v }), disabled: !canWrite }),
1434
+ /* @__PURE__ */ jsx6(ColorSwatch, { label: "BG", value: page.background, onCommit: (v) => onSetPageProps({ background: v }), disabled: !canWrite }),
1879
1435
  SEP,
1880
- /* @__PURE__ */ jsx5(BleedControls, { page, canWrite, onSetPageProps })
1436
+ /* @__PURE__ */ jsx6(BleedControls, { page, canWrite, onSetPageProps })
1881
1437
  ] });
1882
1438
  }
1883
1439
  function BleedControls({ page, canWrite, onSetPageProps }) {
@@ -1887,20 +1443,20 @@ function BleedControls({ page, canWrite, onSetPageProps }) {
1887
1443
  onSetPageProps({ bleed: { ...current, [side]: value } });
1888
1444
  }
1889
1445
  if (!bleed) {
1890
- return /* @__PURE__ */ jsx5(
1446
+ return /* @__PURE__ */ jsx6(
1891
1447
  "button",
1892
1448
  {
1893
1449
  type: "button",
1894
1450
  disabled: !canWrite,
1895
1451
  onClick: () => onSetPageProps({ bleed: { top: 3, right: 3, bottom: 3, left: 3 } }),
1896
- className: BTN2,
1452
+ className: BTN,
1897
1453
  title: "Enable bleed",
1898
- children: /* @__PURE__ */ jsx5(BleedGlyph, { className: "h-3.5 w-3.5" })
1454
+ children: /* @__PURE__ */ jsx6(BleedGlyph, { className: "h-3.5 w-3.5" })
1899
1455
  }
1900
1456
  );
1901
1457
  }
1902
1458
  return /* @__PURE__ */ jsxs5(Fragment4, { children: [
1903
- ["top", "right", "bottom", "left"].map((side) => /* @__PURE__ */ jsx5(
1459
+ ["top", "right", "bottom", "left"].map((side) => /* @__PURE__ */ jsx6(
1904
1460
  NumberInput,
1905
1461
  {
1906
1462
  label: `Bleed ${side[0].toUpperCase()}`,
@@ -1911,13 +1467,13 @@ function BleedControls({ page, canWrite, onSetPageProps }) {
1911
1467
  },
1912
1468
  side
1913
1469
  )),
1914
- /* @__PURE__ */ jsx5(
1470
+ /* @__PURE__ */ jsx6(
1915
1471
  "button",
1916
1472
  {
1917
1473
  type: "button",
1918
1474
  disabled: !canWrite,
1919
1475
  onClick: () => onSetPageProps({ bleed: null }),
1920
- className: BTN2,
1476
+ className: BTN,
1921
1477
  title: "Remove bleed",
1922
1478
  children: "\xD7"
1923
1479
  }
@@ -1926,11 +1482,10 @@ function BleedControls({ page, canWrite, onSetPageProps }) {
1926
1482
  }
1927
1483
 
1928
1484
  // src/design-canvas-react/components/ZoomControls.tsx
1929
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1485
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1930
1486
  var STEP = 0.1;
1931
1487
  var MIN = 0.05;
1932
1488
  var MAX = 32;
1933
- var BTN3 = "flex h-6 w-6 items-center justify-center rounded border border-[var(--border-default)] text-[var(--text-secondary)] transition hover:text-[var(--text-primary)] disabled:cursor-default disabled:opacity-40";
1934
1489
  function ZoomControls({ zoom, onZoom, onFit }) {
1935
1490
  function zoomOut() {
1936
1491
  onZoom(Math.max(MIN, parseFloat((zoom - STEP).toFixed(4))));
@@ -1942,26 +1497,26 @@ function ZoomControls({ zoom, onZoom, onFit }) {
1942
1497
  onZoom(1);
1943
1498
  }
1944
1499
  return /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-1 px-2", children: [
1945
- /* @__PURE__ */ jsx6(
1500
+ /* @__PURE__ */ jsx7(
1946
1501
  "button",
1947
1502
  {
1948
1503
  type: "button",
1949
1504
  "aria-label": "Fit page to viewport",
1950
1505
  onClick: onFit,
1951
- className: BTN3,
1506
+ className: BTN_SM,
1952
1507
  title: "Fit page (F)",
1953
- children: /* @__PURE__ */ jsx6(ZoomFitGlyph, { className: "h-3.5 w-3.5" })
1508
+ children: /* @__PURE__ */ jsx7(ZoomFitGlyph, { className: "h-3.5 w-3.5" })
1954
1509
  }
1955
1510
  ),
1956
- /* @__PURE__ */ jsx6(
1511
+ /* @__PURE__ */ jsx7(
1957
1512
  "button",
1958
1513
  {
1959
1514
  type: "button",
1960
1515
  "aria-label": "Zoom out",
1961
1516
  onClick: zoomOut,
1962
1517
  disabled: zoom <= MIN,
1963
- className: BTN3,
1964
- children: /* @__PURE__ */ jsx6("span", { className: "text-base leading-none", children: "\u2212" })
1518
+ className: BTN_SM,
1519
+ children: /* @__PURE__ */ jsx7("span", { className: "text-base leading-none", children: "\u2212" })
1965
1520
  }
1966
1521
  ),
1967
1522
  /* @__PURE__ */ jsxs6(
@@ -1970,7 +1525,7 @@ function ZoomControls({ zoom, onZoom, onFit }) {
1970
1525
  type: "button",
1971
1526
  "aria-label": "Reset to 100%",
1972
1527
  onClick: resetHundred,
1973
- className: "rounded px-1.5 py-0.5 font-mono text-[11px] tabular-nums text-[var(--text-secondary)] transition hover:bg-[var(--border-default)] hover:text-[var(--text-primary)]",
1528
+ className: "rounded px-1.5 py-0.5 font-mono text-[11px] tabular-nums text-[var(--text-secondary)] transition hover:bg-[var(--border-default)] hover:text-[var(--text-primary)] focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--ring))]",
1974
1529
  title: "Reset to 100%",
1975
1530
  children: [
1976
1531
  Math.round(zoom * 100),
@@ -1978,15 +1533,15 @@ function ZoomControls({ zoom, onZoom, onFit }) {
1978
1533
  ]
1979
1534
  }
1980
1535
  ),
1981
- /* @__PURE__ */ jsx6(
1536
+ /* @__PURE__ */ jsx7(
1982
1537
  "button",
1983
1538
  {
1984
1539
  type: "button",
1985
1540
  "aria-label": "Zoom in",
1986
1541
  onClick: zoomIn,
1987
1542
  disabled: zoom >= MAX,
1988
- className: BTN3,
1989
- children: /* @__PURE__ */ jsx6("span", { className: "text-sm leading-none", children: "+" })
1543
+ className: BTN_SM,
1544
+ children: /* @__PURE__ */ jsx7("span", { className: "text-sm leading-none", children: "+" })
1990
1545
  }
1991
1546
  )
1992
1547
  ] });
@@ -1994,7 +1549,7 @@ function ZoomControls({ zoom, onZoom, onFit }) {
1994
1549
 
1995
1550
  // src/design-canvas-react/components/ExportControl.tsx
1996
1551
  import { useRef as useRef4, useState as useState4 } from "react";
1997
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1552
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1998
1553
  var FORMATS = [
1999
1554
  { id: "png", label: "PNG" },
2000
1555
  { id: "jpeg", label: "JPEG" }
@@ -2028,9 +1583,9 @@ function ExportControl({ defaults, onExport, className }) {
2028
1583
  },
2029
1584
  className: "flex h-8 items-center gap-1.5 rounded-md border border-[var(--border-default)] px-2.5 text-xs font-medium text-[var(--text-primary)] transition-colors hover:border-[var(--brand-primary)]/40",
2030
1585
  children: [
2031
- /* @__PURE__ */ jsx7(ExportGlyph, { className: "h-3.5 w-3.5" }),
1586
+ /* @__PURE__ */ jsx8(ExportGlyph, { className: "h-3.5 w-3.5" }),
2032
1587
  "Export",
2033
- /* @__PURE__ */ jsx7(ChevronDownGlyph, { className: "h-3 w-3 text-[var(--text-muted)]" })
1588
+ /* @__PURE__ */ jsx8(ChevronDownGlyph, { className: "h-3 w-3 text-[var(--text-muted)]" })
2034
1589
  ]
2035
1590
  }
2036
1591
  ),
@@ -2047,8 +1602,8 @@ function ExportControl({ defaults, onExport, className }) {
2047
1602
  className: "absolute right-0 top-full z-50 mt-1 flex w-52 flex-col gap-3 rounded-md border border-[var(--border-default)] bg-[var(--bg-input)] p-3 shadow-lg",
2048
1603
  children: [
2049
1604
  /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-1.5", children: [
2050
- /* @__PURE__ */ jsx7("span", { className: "text-[11px] font-medium text-[var(--text-muted)]", children: "Format" }),
2051
- /* @__PURE__ */ jsx7("div", { className: "flex gap-1.5", children: FORMATS.map((f) => /* @__PURE__ */ jsx7(
1605
+ /* @__PURE__ */ jsx8("span", { className: "text-[11px] font-medium text-[var(--text-muted)]", children: "Format" }),
1606
+ /* @__PURE__ */ jsx8("div", { className: "flex gap-1.5", children: FORMATS.map((f) => /* @__PURE__ */ jsx8(
2052
1607
  "button",
2053
1608
  {
2054
1609
  type: "button",
@@ -2061,8 +1616,8 @@ function ExportControl({ defaults, onExport, className }) {
2061
1616
  )) })
2062
1617
  ] }),
2063
1618
  /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-1.5", children: [
2064
- /* @__PURE__ */ jsx7("span", { className: "text-[11px] font-medium text-[var(--text-muted)]", children: "Scale" }),
2065
- /* @__PURE__ */ jsx7("div", { className: "flex gap-1.5", children: SCALES.map((s) => /* @__PURE__ */ jsx7(
1619
+ /* @__PURE__ */ jsx8("span", { className: "text-[11px] font-medium text-[var(--text-muted)]", children: "Scale" }),
1620
+ /* @__PURE__ */ jsx8("div", { className: "flex gap-1.5", children: SCALES.map((s) => /* @__PURE__ */ jsx8(
2066
1621
  "button",
2067
1622
  {
2068
1623
  type: "button",
@@ -2074,7 +1629,7 @@ function ExportControl({ defaults, onExport, className }) {
2074
1629
  s.value
2075
1630
  )) })
2076
1631
  ] }),
2077
- /* @__PURE__ */ jsx7(
1632
+ /* @__PURE__ */ jsx8(
2078
1633
  "button",
2079
1634
  {
2080
1635
  type: "button",
@@ -2091,7 +1646,7 @@ function ExportControl({ defaults, onExport, className }) {
2091
1646
  }
2092
1647
 
2093
1648
  // src/design-canvas-react/components/DesignCanvas.tsx
2094
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1649
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2095
1650
  function mintId() {
2096
1651
  const uuid = globalThis.crypto && "randomUUID" in globalThis.crypto ? globalThis.crypto.randomUUID() : null;
2097
1652
  return `local-${uuid ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}`;
@@ -2126,6 +1681,7 @@ function DesignCanvas({
2126
1681
  document: initialDocument,
2127
1682
  rev: initialRev,
2128
1683
  canWrite,
1684
+ mode = "edit",
2129
1685
  onApplyOperations,
2130
1686
  onSelectionChange,
2131
1687
  renderAgentPanel,
@@ -2135,6 +1691,7 @@ function DesignCanvas({
2135
1691
  className,
2136
1692
  fitOnMount,
2137
1693
  onReady,
1694
+ render,
2138
1695
  renderWorkspace,
2139
1696
  renderThumbnail
2140
1697
  }) {
@@ -2317,12 +1874,7 @@ function DesignCanvas({
2317
1874
  void onApplyOperations(command.inverseOperations()).then((result) => {
2318
1875
  if (result.document) stack.reset(result.document);
2319
1876
  }).catch((error) => {
2320
- if (stack.canRedo()) {
2321
- try {
2322
- stack.redo();
2323
- } catch {
2324
- }
2325
- }
1877
+ stack.reexecute(command);
2326
1878
  setCommitError(error instanceof Error ? error.message : String(error));
2327
1879
  });
2328
1880
  }, [stack, canWrite, onApplyOperations]);
@@ -2338,12 +1890,7 @@ function DesignCanvas({
2338
1890
  void onApplyOperations(command.operations()).then((result) => {
2339
1891
  if (result.document) stack.reset(result.document);
2340
1892
  }).catch((error) => {
2341
- if (stack.canUndo()) {
2342
- try {
2343
- stack.undo();
2344
- } catch {
2345
- }
2346
- }
1893
+ stack.reundo(command);
2347
1894
  setCommitError(error instanceof Error ? error.message : String(error));
2348
1895
  });
2349
1896
  }, [stack, canWrite, onApplyOperations]);
@@ -2385,8 +1932,9 @@ function DesignCanvas({
2385
1932
  if (!activePage) return [];
2386
1933
  return editorState.selectedElementIds.map((id) => activePage.elements.find((el) => el.id === id)).filter((el) => el !== void 0);
2387
1934
  }, [activePage, editorState.selectedElementIds]);
1935
+ const review = mode === "review";
2388
1936
  if (!activePage) {
2389
- return /* @__PURE__ */ jsx8("div", { className: `flex h-full items-center justify-center bg-[var(--bg-input)] text-[var(--text-muted)] ${className ?? ""}`, children: "No pages in document" });
1937
+ return /* @__PURE__ */ jsx9("div", { className: `flex h-full items-center justify-center bg-[var(--bg-input)] text-[var(--text-muted)] ${className ?? ""}`, children: "No pages in document" });
2390
1938
  }
2391
1939
  const bleedScreen = editorState.showBleed && activePage.bleed ? {
2392
1940
  top: activePage.bleed.top * editorState.zoom,
@@ -2395,15 +1943,16 @@ function DesignCanvas({
2395
1943
  left: activePage.bleed.left * editorState.zoom
2396
1944
  } : null;
2397
1945
  return /* @__PURE__ */ jsxs8("div", { className: `flex h-full min-h-0 bg-[var(--bg-input)] text-[var(--text-primary)] ${className ?? ""}`, children: [
2398
- renderSidePanel ? /* @__PURE__ */ jsx8("aside", { className: "flex w-64 shrink-0 flex-col overflow-hidden border-r border-[var(--border-default)]", children: renderSidePanel() }) : null,
1946
+ renderSidePanel && !review ? /* @__PURE__ */ jsx9("aside", { className: "flex w-64 shrink-0 flex-col overflow-hidden border-r border-[var(--border-default)]", children: renderSidePanel() }) : null,
2399
1947
  /* @__PURE__ */ jsxs8("div", { className: "flex min-w-0 flex-1 flex-col", children: [
2400
1948
  /* @__PURE__ */ jsxs8("div", { className: "flex shrink-0 items-stretch", children: [
2401
- /* @__PURE__ */ jsx8("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx8(
1949
+ /* @__PURE__ */ jsx9("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx9(
2402
1950
  Toolbar,
2403
1951
  {
2404
1952
  page: activePage,
2405
1953
  selectedElements,
2406
1954
  canWrite,
1955
+ mode,
2407
1956
  canUndo: stack.canUndo(),
2408
1957
  canRedo: stack.canRedo(),
2409
1958
  gridEnabled: editorState.gridEnabled,
@@ -2426,7 +1975,7 @@ function DesignCanvas({
2426
1975
  onBindSlot: handleBindSlot
2427
1976
  }
2428
1977
  ) }),
2429
- onExport ? /* @__PURE__ */ jsx8("div", { className: "flex shrink-0 items-center border-b border-l border-[var(--border-default)] bg-[var(--bg-input)] px-2", children: /* @__PURE__ */ jsx8(
1978
+ onExport ? /* @__PURE__ */ jsx9("div", { className: "flex shrink-0 items-center border-b border-l border-[var(--border-default)] bg-[var(--bg-input)] px-2", children: /* @__PURE__ */ jsx9(
2430
1979
  ExportControl,
2431
1980
  {
2432
1981
  defaults: exportDefaults,
@@ -2437,11 +1986,11 @@ function DesignCanvas({
2437
1986
  commitError ? /* @__PURE__ */ jsxs8(
2438
1987
  "div",
2439
1988
  {
2440
- className: "flex shrink-0 items-center justify-between gap-3 border-b border-rose-500/30 bg-rose-500/10 px-3 py-1.5 text-xs text-rose-300",
1989
+ className: "flex shrink-0 items-center justify-between gap-3 border-b border-[hsl(var(--destructive)/0.3)] bg-[hsl(var(--destructive)/0.1)] px-3 py-1.5 text-xs text-[var(--text-danger)]",
2441
1990
  role: "alert",
2442
1991
  children: [
2443
- /* @__PURE__ */ jsx8("span", { className: "min-w-0 truncate", children: commitError }),
2444
- /* @__PURE__ */ jsx8(
1992
+ /* @__PURE__ */ jsx9("span", { className: "min-w-0 truncate", children: commitError }),
1993
+ /* @__PURE__ */ jsx9(
2445
1994
  "button",
2446
1995
  {
2447
1996
  type: "button",
@@ -2454,7 +2003,7 @@ function DesignCanvas({
2454
2003
  }
2455
2004
  ) : null,
2456
2005
  /* @__PURE__ */ jsxs8("div", { className: "relative min-h-0 flex-1", children: [
2457
- /* @__PURE__ */ jsx8(
2006
+ /* @__PURE__ */ jsx9(
2458
2007
  Rulers,
2459
2008
  {
2460
2009
  pageWidth: activePage.width,
@@ -2467,12 +2016,12 @@ function DesignCanvas({
2467
2016
  onGuidesChange: handleSetPageGuides
2468
2017
  }
2469
2018
  ),
2470
- bleedScreen && activePage.bleed ? /* @__PURE__ */ jsx8(
2019
+ bleedScreen && activePage.bleed ? /* @__PURE__ */ jsx9(
2471
2020
  "div",
2472
2021
  {
2473
2022
  className: "pointer-events-none absolute inset-0 z-10 overflow-hidden",
2474
2023
  "aria-hidden": true,
2475
- children: /* @__PURE__ */ jsx8(
2024
+ children: /* @__PURE__ */ jsx9(
2476
2025
  "div",
2477
2026
  {
2478
2027
  style: {
@@ -2480,7 +2029,7 @@ function DesignCanvas({
2480
2029
  left: editorState.panX,
2481
2030
  top: editorState.panY
2482
2031
  },
2483
- children: /* @__PURE__ */ jsx8(
2032
+ children: /* @__PURE__ */ jsx9(
2484
2033
  BleedTrimOverlay,
2485
2034
  {
2486
2035
  pageWidthPx: activePage.width * editorState.zoom,
@@ -2510,18 +2059,20 @@ function DesignCanvas({
2510
2059
  onExportRef: exportRef,
2511
2060
  fitOnMount,
2512
2061
  onReady,
2062
+ render,
2513
2063
  onZoomChange: setZoom,
2514
2064
  onPanChange: setPan,
2515
2065
  onSelectElements: setSelectedElements
2516
2066
  })
2517
2067
  ] }),
2518
2068
  /* @__PURE__ */ jsxs8("div", { className: "flex shrink-0 items-stretch border-t border-[var(--border-default)]", children: [
2519
- /* @__PURE__ */ jsx8("div", { className: "min-w-0 flex-1 overflow-hidden", children: /* @__PURE__ */ jsx8(
2069
+ /* @__PURE__ */ jsx9("div", { className: "min-w-0 flex-1 overflow-hidden", children: /* @__PURE__ */ jsx9(
2520
2070
  PagesStrip,
2521
2071
  {
2522
2072
  pages: editorState.document.pages,
2523
2073
  activePageId: editorState.activePageId,
2524
2074
  canWrite,
2075
+ canManagePages: !review,
2525
2076
  renderThumbnail,
2526
2077
  onSelectPage: setActivePage,
2527
2078
  onAddPage: handleAddPage,
@@ -2530,7 +2081,7 @@ function DesignCanvas({
2530
2081
  onReorderPage: handleReorderPage
2531
2082
  }
2532
2083
  ) }),
2533
- /* @__PURE__ */ jsx8("div", { className: "flex shrink-0 items-center border-l border-[var(--border-default)]", children: /* @__PURE__ */ jsx8(
2084
+ /* @__PURE__ */ jsx9("div", { className: "flex shrink-0 items-center border-l border-[var(--border-default)]", children: /* @__PURE__ */ jsx9(
2534
2085
  ZoomControls,
2535
2086
  {
2536
2087
  zoom: editorState.zoom,
@@ -2540,29 +2091,12 @@ function DesignCanvas({
2540
2091
  ) })
2541
2092
  ] })
2542
2093
  ] }),
2543
- renderAgentPanel ? /* @__PURE__ */ jsx8("aside", { className: "flex w-80 shrink-0 flex-col overflow-hidden border-l border-[var(--border-default)]", children: renderAgentPanel({ selectedElements, activePageId: editorState.activePageId }) }) : null
2094
+ renderAgentPanel ? /* @__PURE__ */ jsx9("aside", { className: "flex w-80 shrink-0 flex-col overflow-hidden border-l border-[var(--border-default)]", children: renderAgentPanel({ selectedElements, activePageId: editorState.activePageId }) }) : null
2544
2095
  ] });
2545
2096
  }
2546
2097
  var DesignCanvas_default = DesignCanvas;
2547
2098
 
2548
2099
  export {
2549
- SCENE_COMMAND_HISTORY_LIMIT,
2550
- createSceneCommandStack,
2551
- addElementCommand,
2552
- setAttrsCommand,
2553
- multiSetAttrsCommand,
2554
- reorderElementCommand,
2555
- deleteElementCommand,
2556
- groupElementsCommand,
2557
- ungroupElementCommand,
2558
- addPageCommand,
2559
- duplicatePageCommand,
2560
- deletePageCommand,
2561
- reorderPageCommand,
2562
- setPagePropsCommand,
2563
- setPageGuidesCommand,
2564
- bindSlotCommand,
2565
- setDocumentTitleCommand,
2566
2100
  selectTickStep,
2567
2101
  buildRulerTicks,
2568
2102
  formatRulerLabel,
@@ -2580,6 +2114,11 @@ export {
2580
2114
  VideoGlyph,
2581
2115
  SlotGlyph,
2582
2116
  ShapesGlyph,
2117
+ BTN,
2118
+ BTN_ACTIVE,
2119
+ BTN_SM,
2120
+ BTN_SM_ACTIVE,
2121
+ IconButton,
2583
2122
  PagesStrip,
2584
2123
  Rulers,
2585
2124
  Toolbar,
@@ -2588,4 +2127,4 @@ export {
2588
2127
  DesignCanvas,
2589
2128
  DesignCanvas_default
2590
2129
  };
2591
- //# sourceMappingURL=chunk-LPAVBGVY.js.map
2130
+ //# sourceMappingURL=chunk-YEWTVDDT.js.map