@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,6 +1,19 @@
1
1
  import {
2
2
  BleedTrimOverlay,
3
- DesignCanvas,
3
+ DesignCanvas
4
+ } from "./chunk-YEWTVDDT.js";
5
+ import {
6
+ collectGridTargets,
7
+ createSnapEngine,
8
+ createZoomPanMath,
9
+ documentCropToStageCoords,
10
+ hitTestPoint,
11
+ identifyTaintedSrc,
12
+ isExportHiddenNodeName,
13
+ marqueeSelect,
14
+ resolveExportParams
15
+ } from "./chunk-V5XZKHCL.js";
16
+ import {
4
17
  addElementCommand,
5
18
  createSceneCommandStack,
6
19
  deleteElementCommand,
@@ -8,15 +21,16 @@ import {
8
21
  multiSetAttrsCommand,
9
22
  setAttrsCommand,
10
23
  ungroupElementCommand
11
- } from "./chunk-LPAVBGVY.js";
24
+ } from "./chunk-XWJXZ6WE.js";
12
25
  import {
13
26
  SCENE_SCHEMA_VERSION,
14
- bleedAwareExportBounds,
15
27
  boundsIntersect,
16
28
  elementAabb,
17
- findElement,
18
- scaleForPreset
19
- } from "./chunk-JZAJE3JL.js";
29
+ findElement
30
+ } from "./chunk-M2EBUDZ7.js";
31
+ import {
32
+ lightTheme
33
+ } from "./chunk-Y2SZVW35.js";
20
34
 
21
35
  // src/design-canvas-react/components/Workspace.tsx
22
36
  import {
@@ -27,42 +41,7 @@ import {
27
41
  useRef as useRef4,
28
42
  useState as useState2
29
43
  } from "react";
30
- import { Stage, Layer as Layer4, Rect as Rect2, Group as Group2 } from "react-konva";
31
-
32
- // src/design-canvas-react/export-math.ts
33
- function isExportHiddenNodeName(name) {
34
- return name.startsWith("overlay:") || name === "Transformer";
35
- }
36
- function resolveExportParams(page, opts) {
37
- const includeBleed = opts.preset !== void 0 ? opts.preset.includeBleed : opts.includeBleed ?? false;
38
- const cropRect = bleedAwareExportBounds(page, includeBleed);
39
- const pixelRatio = opts.preset !== void 0 ? scaleForPreset(opts.preset, cropRect) : opts.pixelRatio ?? 1;
40
- const format = opts.preset !== void 0 ? opts.preset.format : opts.format;
41
- const mimeType = format === "jpeg" ? "image/jpeg" : "image/png";
42
- return {
43
- cropRect,
44
- pixelRatio,
45
- mimeType,
46
- quality: format === "jpeg" ? 0.92 : void 0
47
- };
48
- }
49
- function identifyTaintedSrc(imageSrcs) {
50
- for (const { src } of imageSrcs) {
51
- if (isCrossOriginSrc(src)) return src;
52
- }
53
- return null;
54
- }
55
- function isCrossOriginSrc(src) {
56
- return /^https?:\/\//i.test(src);
57
- }
58
- function documentCropToStageCoords(cropRect, stageScale, stageX, stageY) {
59
- return {
60
- x: stageX + cropRect.x * stageScale,
61
- y: stageY + cropRect.y * stageScale,
62
- width: cropRect.width * stageScale,
63
- height: cropRect.height * stageScale
64
- };
65
- }
44
+ import { Stage, Layer as Layer4, Rect as Rect2, Group as Group3 } from "react-konva";
66
45
 
67
46
  // src/design-canvas-react/export.ts
68
47
  async function exportPageDataUrl(stage, page, opts) {
@@ -144,227 +123,8 @@ function downloadDataUrl(dataUrl, filename) {
144
123
  globalThis.document.body.removeChild(a);
145
124
  }
146
125
 
147
- // src/design-canvas-react/engine/snap.ts
148
- var KIND_PRIORITY = {
149
- "guide": 0,
150
- "page-edge": 1,
151
- "page-center": 2,
152
- "element-edge": 3,
153
- "element-center": 4,
154
- "grid": 5
155
- };
156
- function createSnapEngine() {
157
- return {
158
- collectTargets(state, excludeIds) {
159
- const page = state.document.pages.find((p) => p.id === state.activePageId);
160
- if (!page) throw new Error(`collectTargets: active page ${state.activePageId} not found`);
161
- const vertical = [];
162
- const horizontal = [];
163
- const excludeSet = new Set(excludeIds);
164
- vertical.push({ position: 0, kind: "page-edge" });
165
- vertical.push({ position: page.width, kind: "page-edge" });
166
- vertical.push({ position: page.width / 2, kind: "page-center" });
167
- horizontal.push({ position: 0, kind: "page-edge" });
168
- horizontal.push({ position: page.height, kind: "page-edge" });
169
- horizontal.push({ position: page.height / 2, kind: "page-center" });
170
- for (const pos of page.guides.vertical) {
171
- vertical.push({ position: pos, kind: "guide" });
172
- }
173
- for (const pos of page.guides.horizontal) {
174
- horizontal.push({ position: pos, kind: "guide" });
175
- }
176
- collectElementTargets(page.elements, excludeSet, vertical, horizontal);
177
- return { vertical, horizontal };
178
- },
179
- apply(bounds, targets, thresholdPx, zoom) {
180
- if (!Number.isFinite(zoom) || zoom <= 0) {
181
- throw new Error(`snap.apply: zoom must be a positive finite number, got ${zoom}`);
182
- }
183
- if (!Number.isFinite(thresholdPx) || thresholdPx < 0) {
184
- throw new Error(`snap.apply: thresholdPx must be a non-negative finite number, got ${thresholdPx}`);
185
- }
186
- const threshold = thresholdPx / zoom;
187
- const snappedX = snapAxis(bounds.x, bounds.x + bounds.width / 2, bounds.x + bounds.width, targets.vertical, threshold);
188
- const snappedY = snapAxis(bounds.y, bounds.y + bounds.height / 2, bounds.y + bounds.height, targets.horizontal, threshold);
189
- return {
190
- x: snappedX !== null ? snappedX.docPosition - snappedX.elementOffset : bounds.x,
191
- y: snappedY !== null ? snappedY.docPosition - snappedY.elementOffset : bounds.y,
192
- activeVertical: snappedX !== null ? snappedX.target : null,
193
- activeHorizontal: snappedY !== null ? snappedY.target : null
194
- };
195
- }
196
- };
197
- }
198
- function collectGridTargets(bounds, gridSize, page, thresholdDocPx) {
199
- if (!Number.isFinite(gridSize) || gridSize <= 0) {
200
- throw new Error(`collectGridTargets: gridSize must be positive finite, got ${gridSize}`);
201
- }
202
- const vertical = [];
203
- const horizontal = [];
204
- const xMin = Math.floor((bounds.x - thresholdDocPx) / gridSize) * gridSize;
205
- const xMax = Math.ceil((bounds.x + bounds.width + thresholdDocPx) / gridSize) * gridSize;
206
- for (let x = xMin; x <= xMax; x += gridSize) {
207
- if (x >= 0 && x <= page.width) vertical.push({ position: x, kind: "grid" });
208
- }
209
- const yMin = Math.floor((bounds.y - thresholdDocPx) / gridSize) * gridSize;
210
- const yMax = Math.ceil((bounds.y + bounds.height + thresholdDocPx) / gridSize) * gridSize;
211
- for (let y = yMin; y <= yMax; y += gridSize) {
212
- if (y >= 0 && y <= page.height) horizontal.push({ position: y, kind: "grid" });
213
- }
214
- return { vertical, horizontal };
215
- }
216
- function snapAxis(start, center, end, targets, threshold) {
217
- const candidates = [
218
- { value: start, offset: 0 },
219
- { value: center, offset: center - start },
220
- { value: end, offset: end - start }
221
- ];
222
- let best = null;
223
- let bestDistance = Infinity;
224
- let bestPriority = Infinity;
225
- for (const { value, offset } of candidates) {
226
- for (const target of targets) {
227
- const distance = Math.abs(target.position - value);
228
- if (distance > threshold) continue;
229
- const priority = KIND_PRIORITY[target.kind];
230
- if (distance < bestDistance || distance === bestDistance && priority < bestPriority) {
231
- bestDistance = distance;
232
- bestPriority = priority;
233
- best = { target, docPosition: target.position, elementOffset: offset };
234
- }
235
- }
236
- }
237
- return best;
238
- }
239
- function collectElementTargets(elements, excludeIds, vertical, horizontal) {
240
- for (const el of elements) {
241
- if (!el.visible || el.locked) continue;
242
- if (excludeIds.has(el.id)) continue;
243
- const aabb = elementAabb(el);
244
- vertical.push({ position: aabb.x, kind: "element-edge" });
245
- vertical.push({ position: aabb.x + aabb.width / 2, kind: "element-center" });
246
- vertical.push({ position: aabb.x + aabb.width, kind: "element-edge" });
247
- horizontal.push({ position: aabb.y, kind: "element-edge" });
248
- horizontal.push({ position: aabb.y + aabb.height / 2, kind: "element-center" });
249
- horizontal.push({ position: aabb.y + aabb.height, kind: "element-edge" });
250
- if (el.kind === "group") {
251
- collectElementTargets(el.children, excludeIds, vertical, horizontal);
252
- }
253
- }
254
- }
255
-
256
- // src/design-canvas-react/engine/zoom-pan.ts
257
- function createZoomPanMath(config) {
258
- const { minZoom, maxZoom } = config;
259
- if (!Number.isFinite(minZoom) || minZoom <= 0) {
260
- throw new Error(`minZoom must be a positive finite number, got ${minZoom}`);
261
- }
262
- if (!Number.isFinite(maxZoom) || maxZoom <= minZoom) {
263
- throw new Error(`maxZoom must be finite and greater than minZoom (${minZoom}), got ${maxZoom}`);
264
- }
265
- return {
266
- minZoom,
267
- maxZoom,
268
- zoomAtPoint(state, factor, screenX, screenY) {
269
- if (!Number.isFinite(factor) || factor <= 0) {
270
- throw new Error(`zoomAtPoint: factor must be a positive finite number, got ${factor}`);
271
- }
272
- if (!Number.isFinite(screenX) || !Number.isFinite(screenY)) {
273
- throw new Error(`zoomAtPoint: screenX/screenY must be finite numbers`);
274
- }
275
- if (!Number.isFinite(state.zoom) || state.zoom <= 0) {
276
- throw new Error(`zoomAtPoint: state.zoom must be a positive finite number, got ${state.zoom}`);
277
- }
278
- const newZoom = Math.min(maxZoom, Math.max(minZoom, state.zoom * factor));
279
- const docX = (screenX - state.panX) / state.zoom;
280
- const docY = (screenY - state.panY) / state.zoom;
281
- const panX = screenX - docX * newZoom;
282
- const panY = screenY - docY * newZoom;
283
- return { zoom: newZoom, panX, panY };
284
- },
285
- fitPage(page, viewport, paddingPx = 48) {
286
- if (!Number.isFinite(page.width) || page.width <= 0) {
287
- throw new Error(`fitPage: page.width must be a positive finite number, got ${page.width}`);
288
- }
289
- if (!Number.isFinite(page.height) || page.height <= 0) {
290
- throw new Error(`fitPage: page.height must be a positive finite number, got ${page.height}`);
291
- }
292
- if (!Number.isFinite(viewport.width) || viewport.width <= 0) {
293
- throw new Error(`fitPage: viewport.width must be a positive finite number, got ${viewport.width}`);
294
- }
295
- if (!Number.isFinite(viewport.height) || viewport.height <= 0) {
296
- throw new Error(`fitPage: viewport.height must be a positive finite number, got ${viewport.height}`);
297
- }
298
- if (!Number.isFinite(paddingPx) || paddingPx < 0) {
299
- throw new Error(`fitPage: paddingPx must be a non-negative finite number, got ${paddingPx}`);
300
- }
301
- const availW = viewport.width - paddingPx * 2;
302
- const availH = viewport.height - paddingPx * 2;
303
- const zoom = Math.min(maxZoom, Math.max(minZoom, Math.min(availW / page.width, availH / page.height)));
304
- const panX = (viewport.width - page.width * zoom) / 2;
305
- const panY = (viewport.height - page.height * zoom) / 2;
306
- return { zoom, panX, panY };
307
- },
308
- documentToScreen(state, x, y) {
309
- return { x: x * state.zoom + state.panX, y: y * state.zoom + state.panY };
310
- },
311
- screenToDocument(state, x, y) {
312
- if (!Number.isFinite(state.zoom) || state.zoom === 0) {
313
- throw new Error(`screenToDocument: zoom must be a non-zero finite number, got ${state.zoom}`);
314
- }
315
- return { x: (x - state.panX) / state.zoom, y: (y - state.panY) / state.zoom };
316
- }
317
- };
318
- }
319
-
320
- // src/design-canvas-react/engine/selection.ts
321
- function marqueeSelect(page, rect, opts = {}) {
322
- assertValidBounds(rect, "marqueeSelect rect");
323
- const result = [];
324
- collectMarqueeIds(page.elements, rect, opts.requireFullContainment ?? false, result);
325
- return result;
326
- }
327
- function collectMarqueeIds(elements, rect, requireContainment, result) {
328
- for (const el of elements) {
329
- if (!el.visible || el.locked) continue;
330
- const aabb = elementAabb(el);
331
- if (el.kind === "group") {
332
- if (boundsIntersect(rect, aabb)) {
333
- collectMarqueeIds(el.children, rect, requireContainment, result);
334
- }
335
- continue;
336
- }
337
- const hit = requireContainment ? boundsContain(rect, aabb) : boundsIntersect(rect, aabb);
338
- if (hit) result.push(el.id);
339
- }
340
- }
341
- function boundsContain(outer, inner) {
342
- return inner.x >= outer.x && inner.y >= outer.y && inner.x + inner.width <= outer.x + outer.width && inner.y + inner.height <= outer.y + outer.height;
343
- }
344
- var NUDGE_NORMAL_PX = 1;
345
- var NUDGE_SHIFT_PX = 10;
346
- function nudgeDelta(key, shift) {
347
- const step = shift ? NUDGE_SHIFT_PX : NUDGE_NORMAL_PX;
348
- switch (key) {
349
- case "ArrowLeft":
350
- return { dx: -step, dy: 0 };
351
- case "ArrowRight":
352
- return { dx: step, dy: 0 };
353
- case "ArrowUp":
354
- return { dx: 0, dy: -step };
355
- case "ArrowDown":
356
- return { dx: 0, dy: step };
357
- }
358
- }
359
- var DUPLICATE_OFFSET = { dx: 10, dy: 10 };
360
- function assertValidBounds(bounds, label) {
361
- if (!Number.isFinite(bounds.x) || !Number.isFinite(bounds.y) || !Number.isFinite(bounds.width) || !Number.isFinite(bounds.height)) {
362
- throw new Error(`${label}: all fields must be finite numbers`);
363
- }
364
- }
365
-
366
126
  // src/design-canvas-react/components/ElementNode.tsx
367
- import { useEffect, useRef, useState } from "react";
127
+ import { memo, useEffect, useRef, useState } from "react";
368
128
  import { Group, Rect, Ellipse, Line, Text, Image as KonvaImage } from "react-konva";
369
129
 
370
130
  // src/design-canvas-react/components/transform-math.ts
@@ -451,7 +211,7 @@ function snapRotation(angleDeg, thresholdDeg = 5) {
451
211
  }
452
212
  return bestDist <= thresholdDeg ? best : normalized;
453
213
  }
454
- function nudgeDelta2(key, shift) {
214
+ function nudgeDelta(key, shift) {
455
215
  const step = shift ? 10 : 1;
456
216
  switch (key) {
457
217
  case "ArrowLeft":
@@ -497,7 +257,7 @@ function useImage(src) {
497
257
  }, [src]);
498
258
  return imageCache.get(src) ?? null;
499
259
  }
500
- function ElementNode(props) {
260
+ function ElementNodeImpl(props) {
501
261
  const { element } = props;
502
262
  if (!element.visible) return null;
503
263
  switch (element.kind) {
@@ -517,33 +277,55 @@ function ElementNode(props) {
517
277
  return /* @__PURE__ */ jsx(GroupNode, { ...props, element });
518
278
  }
519
279
  }
280
+ var ElementNode = memo(ElementNodeImpl);
520
281
  function useDragBindings(props, originX, originY) {
521
282
  const { element, onClick, onDragStart, onDragMove, onDragEnd, onDoubleClick } = props;
522
283
  const isDraggable = !element.locked && !!onDragEnd;
523
284
  const originRef = useRef({ x: originX, y: originY });
524
285
  originRef.current = { x: originX, y: originY };
286
+ const [dragging, setDragging] = useState(false);
525
287
  return {
526
288
  draggable: isDraggable,
527
289
  // locked elements still receive click for selection; listening must be true.
528
290
  listening: true,
291
+ dragging,
529
292
  onDragStart: isDraggable ? () => {
293
+ setDragging(true);
530
294
  onDragStart?.(element.id);
531
295
  } : void 0,
532
296
  onDragMove: isDraggable ? (e) => {
533
297
  onDragMove?.(element.id, e.target.x() - originRef.current.x, e.target.y() - originRef.current.y);
534
298
  } : void 0,
535
299
  onDragEnd: isDraggable ? (e) => {
300
+ setDragging(false);
536
301
  onDragEnd?.(element.id, e.target.x(), e.target.y());
537
302
  } : void 0,
538
303
  onClick: () => onClick?.(element.id),
539
304
  onDblClick: () => onDoubleClick?.(element.id)
540
305
  };
541
306
  }
307
+ function useNodeCache(ref, isSelected, dragging, zoom, invalidateKey) {
308
+ useEffect(() => {
309
+ const node = ref.current;
310
+ if (!node) return;
311
+ if (isSelected || dragging) {
312
+ node.clearCache();
313
+ return;
314
+ }
315
+ node.cache();
316
+ return () => {
317
+ node.clearCache();
318
+ };
319
+ }, [ref, isSelected, dragging, zoom, invalidateKey]);
320
+ }
542
321
  function RectNode({ element, ...rest }) {
543
- const drag = useDragBindings({ element, ...rest }, element.x, element.y);
322
+ const { dragging, ...drag } = useDragBindings({ element, ...rest }, element.x, element.y);
323
+ const ref = useRef(null);
324
+ useNodeCache(ref, rest.isSelected, dragging, rest.zoom, element);
544
325
  return /* @__PURE__ */ jsx(
545
326
  Rect,
546
327
  {
328
+ ref,
547
329
  name: element.id,
548
330
  x: element.x,
549
331
  y: element.y,
@@ -561,10 +343,13 @@ function RectNode({ element, ...rest }) {
561
343
  }
562
344
  function EllipseNode({ element, ...rest }) {
563
345
  const { x: cx, y: cy, radiusX, radiusY } = ellipseCenterFromTopLeft(element);
564
- const drag = useDragBindings({ element, ...rest }, element.x, element.y);
346
+ const { dragging, ...drag } = useDragBindings({ element, ...rest }, element.x, element.y);
347
+ const ref = useRef(null);
348
+ useNodeCache(ref, rest.isSelected, dragging, rest.zoom, element);
565
349
  return /* @__PURE__ */ jsx(
566
350
  Ellipse,
567
351
  {
352
+ ref,
568
353
  name: element.id,
569
354
  x: cx,
570
355
  y: cy,
@@ -580,10 +365,13 @@ function EllipseNode({ element, ...rest }) {
580
365
  );
581
366
  }
582
367
  function LineNode({ element, ...rest }) {
583
- const drag = useDragBindings({ element, ...rest }, element.x, element.y);
368
+ const { dragging, ...drag } = useDragBindings({ element, ...rest }, element.x, element.y);
369
+ const ref = useRef(null);
370
+ useNodeCache(ref, rest.isSelected, dragging, rest.zoom, element);
584
371
  return /* @__PURE__ */ jsx(
585
372
  Line,
586
373
  {
374
+ ref,
587
375
  name: element.id,
588
376
  x: element.x,
589
377
  y: element.y,
@@ -604,11 +392,14 @@ var FONT_STYLE_MAP = {
604
392
  "bold italic": { fontStyle: "italic", fontVariant: "normal", fontWeight: "bold" }
605
393
  };
606
394
  function TextNode({ element, ...rest }) {
607
- const drag = useDragBindings({ element, ...rest }, element.x, element.y);
395
+ const { dragging, ...drag } = useDragBindings({ element, ...rest }, element.x, element.y);
396
+ const ref = useRef(null);
397
+ useNodeCache(ref, rest.isSelected, dragging, rest.zoom, element);
608
398
  const { fontStyle, fontWeight } = FONT_STYLE_MAP[element.fontStyle];
609
399
  return /* @__PURE__ */ jsx(
610
400
  Text,
611
401
  {
402
+ ref,
612
403
  name: element.id,
613
404
  x: element.x,
614
405
  y: element.y,
@@ -627,15 +418,24 @@ function TextNode({ element, ...rest }) {
627
418
  }
628
419
  );
629
420
  }
630
- var BROKEN_FILL = "#e5e7eb";
631
- var BROKEN_STROKE = "#9ca3af";
632
421
  function ImageNode({ element, ...rest }) {
422
+ const render = rest.render ?? lightTheme.canvasRender;
633
423
  const img = useImage(element.src);
634
- const drag = useDragBindings({ element, ...rest }, element.x, element.y);
424
+ const { dragging, ...drag } = useDragBindings({ element, ...rest }, element.x, element.y);
425
+ const ref = useRef(null);
426
+ const imgReady = !!img && img.complete && img.naturalWidth > 0;
427
+ useNodeCache(
428
+ ref,
429
+ rest.isSelected,
430
+ dragging,
431
+ rest.zoom,
432
+ `${imgReady}:${element.src}:${element.width}x${element.height}:${element.fit}`
433
+ );
635
434
  if (!img || !img.complete || img.naturalWidth === 0) {
636
435
  return /* @__PURE__ */ jsx(
637
436
  Rect,
638
437
  {
438
+ ref,
639
439
  name: element.id,
640
440
  x: element.x,
641
441
  y: element.y,
@@ -643,8 +443,8 @@ function ImageNode({ element, ...rest }) {
643
443
  height: element.height,
644
444
  rotation: element.rotation,
645
445
  opacity: element.opacity,
646
- fill: BROKEN_FILL,
647
- stroke: BROKEN_STROKE,
446
+ fill: render.brokenFill,
447
+ stroke: render.brokenStroke,
648
448
  strokeWidth: 1,
649
449
  ...drag
650
450
  }
@@ -679,6 +479,7 @@ function ImageNode({ element, ...rest }) {
679
479
  return /* @__PURE__ */ jsx(
680
480
  KonvaImage,
681
481
  {
482
+ ref,
682
483
  name: element.id,
683
484
  x: element.x,
684
485
  y: element.y,
@@ -693,12 +494,23 @@ function ImageNode({ element, ...rest }) {
693
494
  );
694
495
  }
695
496
  function VideoNode({ element, ...rest }) {
497
+ const render = rest.render ?? lightTheme.canvasRender;
696
498
  const img = useImage(element.posterSrc ?? "");
697
- const drag = useDragBindings({ element, ...rest }, element.x, element.y);
499
+ const { dragging, ...drag } = useDragBindings({ element, ...rest }, element.x, element.y);
500
+ const ref = useRef(null);
501
+ const posterReady = !!element.posterSrc && !!img && img.complete && img.naturalWidth > 0;
502
+ useNodeCache(
503
+ ref,
504
+ rest.isSelected,
505
+ dragging,
506
+ rest.zoom,
507
+ `${posterReady}:${element.posterSrc ?? ""}:${element.width}x${element.height}`
508
+ );
698
509
  if (!element.posterSrc || !img || !img.complete || img.naturalWidth === 0) {
699
510
  return /* @__PURE__ */ jsx(
700
511
  Rect,
701
512
  {
513
+ ref,
702
514
  name: element.id,
703
515
  x: element.x,
704
516
  y: element.y,
@@ -706,8 +518,8 @@ function VideoNode({ element, ...rest }) {
706
518
  height: element.height,
707
519
  rotation: element.rotation,
708
520
  opacity: element.opacity,
709
- fill: "#1f2937",
710
- stroke: "#374151",
521
+ fill: render.placeholderFill,
522
+ stroke: render.placeholderStroke,
711
523
  strokeWidth: 1,
712
524
  ...drag
713
525
  }
@@ -716,6 +528,7 @@ function VideoNode({ element, ...rest }) {
716
528
  return /* @__PURE__ */ jsx(
717
529
  KonvaImage,
718
530
  {
531
+ ref,
719
532
  name: element.id,
720
533
  x: element.x,
721
534
  y: element.y,
@@ -729,7 +542,8 @@ function VideoNode({ element, ...rest }) {
729
542
  );
730
543
  }
731
544
  function GroupNode({ element, ...rest }) {
732
- const drag = useDragBindings({ element, ...rest }, element.x, element.y);
545
+ const { dragging: _dragging, ...drag } = useDragBindings({ element, ...rest }, element.x, element.y);
546
+ void _dragging;
733
547
  return /* @__PURE__ */ jsx(
734
548
  Group,
735
549
  {
@@ -755,7 +569,8 @@ function SelectionLayer({
755
569
  selectedElements,
756
570
  canWrite,
757
571
  onTransformEnd,
758
- pageId
572
+ pageId,
573
+ render = lightTheme.canvasRender
759
574
  }) {
760
575
  const trRef = useRef2(null);
761
576
  useEffect2(() => {
@@ -788,22 +603,35 @@ function SelectionLayer({
788
603
  if (entries.length > 0) onTransformEnd(entries);
789
604
  }
790
605
  const multiSelect = selectedIds.length > 1;
791
- return /* @__PURE__ */ jsx2(Layer, { name: "overlay:selection", listening: false, children: /* @__PURE__ */ jsx2(
792
- Transformer,
793
- {
794
- ref: trRef,
795
- name: "overlay:transformer",
796
- keepRatio: multiSelect,
797
- rotationSnaps: [0, 45, 90, 135, 180, 225, 270, 315],
798
- rotationSnapTolerance: 5,
799
- boundBoxFunc: (oldBox, newBox) => {
800
- if (newBox.width < MIN_SIZE || newBox.height < MIN_SIZE) return oldBox;
801
- return newBox;
802
- },
803
- listening: canWrite,
804
- onTransformEnd: canWrite ? handleTransformEnd : void 0
805
- }
806
- ) });
606
+ return (
607
+ // The Layer must participate in the hit graph (listening) or the Transformer
608
+ // anchors below receive no pointer events and resize/rotate is dead. We scope
609
+ // listening to canWrite so a read-only canvas stays fully click-through.
610
+ // Click-through to elements for selection is preserved: this layer paints
611
+ // only the Transformer, whose anchors are the sole hit targets — empty
612
+ // regions have no shapes, so pointer hits fall through to the content layer.
613
+ // Export exclusion is unaffected: export.ts hides nodes by the 'overlay:'
614
+ // name prefix, not by `listening` (see export-math.isExportHiddenNodeName).
615
+ /* @__PURE__ */ jsx2(Layer, { name: "overlay:selection", listening: canWrite, children: /* @__PURE__ */ jsx2(
616
+ Transformer,
617
+ {
618
+ ref: trRef,
619
+ name: "overlay:transformer",
620
+ keepRatio: multiSelect,
621
+ rotationSnaps: [0, 45, 90, 135, 180, 225, 270, 315],
622
+ rotationSnapTolerance: 5,
623
+ borderStroke: render.selectionStroke,
624
+ anchorStroke: render.selectionStroke,
625
+ anchorFill: render.selectionAnchorFill,
626
+ boundBoxFunc: (oldBox, newBox) => {
627
+ if (newBox.width < MIN_SIZE || newBox.height < MIN_SIZE) return oldBox;
628
+ return newBox;
629
+ },
630
+ listening: canWrite,
631
+ onTransformEnd: canWrite ? handleTransformEnd : void 0
632
+ }
633
+ ) })
634
+ );
807
635
  }
808
636
  function elementToPriorAttrs(element) {
809
637
  const base = {
@@ -896,78 +724,89 @@ function bakeNodeToAttrs(node, element) {
896
724
  }
897
725
 
898
726
  // src/design-canvas-react/components/GridLayer.tsx
899
- import { Layer as Layer2, Line as Line2 } from "react-konva";
900
- import { jsx as jsx3, jsxs } from "react/jsx-runtime";
727
+ import { Layer as Layer2, Group as Group2, Shape } from "react-konva";
728
+ import { jsx as jsx3 } from "react/jsx-runtime";
729
+ function gridVerticalLines(pageWidth, gridSize) {
730
+ const out = [];
731
+ if (gridSize <= 0) return out;
732
+ for (let x = gridSize; x < pageWidth; x += gridSize) out.push(x);
733
+ return out;
734
+ }
735
+ function gridHorizontalLines(pageHeight, gridSize) {
736
+ const out = [];
737
+ if (gridSize <= 0) return out;
738
+ for (let y = gridSize; y < pageHeight; y += gridSize) out.push(y);
739
+ return out;
740
+ }
901
741
  function GridLayer({
902
742
  pageWidth,
903
743
  pageHeight,
904
744
  gridSize,
905
745
  zoom,
746
+ panX,
747
+ panY,
906
748
  color = "#c0c0c0",
907
749
  opacity = 0.5
908
750
  }) {
909
751
  if (!gridVisible(gridSize, zoom, 4)) return null;
910
- const verticals = [];
911
- for (let x = gridSize; x < pageWidth; x += gridSize) {
912
- verticals.push(x);
913
- }
914
- const horizontals = [];
915
- for (let y = gridSize; y < pageHeight; y += gridSize) {
916
- horizontals.push(y);
917
- }
918
- return /* @__PURE__ */ jsxs(Layer2, { name: "overlay:grid", listening: false, children: [
919
- verticals.map((x) => /* @__PURE__ */ jsx3(
920
- Line2,
921
- {
922
- name: "overlay:grid-line",
923
- points: [x, 0, x, pageHeight],
924
- stroke: color,
925
- strokeWidth: 1 / zoom,
926
- opacity,
927
- listening: false,
928
- perfectDrawEnabled: false
929
- },
930
- `v-${x}`
931
- )),
932
- horizontals.map((y) => /* @__PURE__ */ jsx3(
933
- Line2,
934
- {
935
- name: "overlay:grid-line",
936
- points: [0, y, pageWidth, y],
937
- stroke: color,
938
- strokeWidth: 1 / zoom,
939
- opacity,
940
- listening: false,
941
- perfectDrawEnabled: false
942
- },
943
- `h-${y}`
944
- ))
945
- ] });
752
+ const verticals = gridVerticalLines(pageWidth, gridSize);
753
+ const horizontals = gridHorizontalLines(pageHeight, gridSize);
754
+ const sceneFunc = (ctx, shape) => {
755
+ ctx.beginPath();
756
+ for (const x of verticals) {
757
+ ctx.moveTo(x, 0);
758
+ ctx.lineTo(x, pageHeight);
759
+ }
760
+ for (const y of horizontals) {
761
+ ctx.moveTo(0, y);
762
+ ctx.lineTo(pageWidth, y);
763
+ }
764
+ ctx.setAttr("strokeStyle", color);
765
+ ctx.setAttr("lineWidth", 1 / zoom);
766
+ ctx.stroke();
767
+ void shape;
768
+ };
769
+ return /* @__PURE__ */ jsx3(Layer2, { name: "overlay:grid", listening: false, children: /* @__PURE__ */ jsx3(Group2, { x: panX, y: panY, scaleX: zoom, scaleY: zoom, children: /* @__PURE__ */ jsx3(
770
+ Shape,
771
+ {
772
+ name: "overlay:grid-shape",
773
+ sceneFunc,
774
+ stroke: color,
775
+ strokeWidth: 1 / zoom,
776
+ opacity,
777
+ listening: false,
778
+ perfectDrawEnabled: false
779
+ }
780
+ ) }) });
946
781
  }
947
782
 
948
783
  // src/design-canvas-react/components/SnapGuidesOverlay.tsx
949
- import { Layer as Layer3, Line as Line3 } from "react-konva";
950
- import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
951
- var KIND_COLOR = {
952
- "grid": "#a0a0a0",
953
- "guide": "#3b82f6",
954
- "page-edge": "#f59e0b",
955
- "page-center": "#f59e0b",
956
- "element-edge": "#f43f5e",
957
- "element-center": "#f43f5e"
958
- };
784
+ import { Layer as Layer3, Line as Line2 } from "react-konva";
785
+ import { jsx as jsx4, jsxs } from "react/jsx-runtime";
786
+ function kindColors(render) {
787
+ return {
788
+ "grid": render.snapGrid,
789
+ "guide": render.snapGuide,
790
+ "page-edge": render.snapPage,
791
+ "page-center": render.snapPage,
792
+ "element-edge": render.snapElement,
793
+ "element-center": render.snapElement
794
+ };
795
+ }
959
796
  function SnapGuidesOverlay({
960
797
  pageWidth,
961
798
  pageHeight,
962
799
  activeVertical,
963
800
  activeHorizontal,
964
- zoom
801
+ zoom,
802
+ render = lightTheme.canvasRender
965
803
  }) {
966
804
  if (!activeVertical && !activeHorizontal) return null;
805
+ const KIND_COLOR = kindColors(render);
967
806
  const strokeWidth = 1 / zoom;
968
- return /* @__PURE__ */ jsxs2(Layer3, { name: "overlay:snap", listening: false, children: [
807
+ return /* @__PURE__ */ jsxs(Layer3, { name: "overlay:snap", listening: false, children: [
969
808
  activeVertical && /* @__PURE__ */ jsx4(
970
- Line3,
809
+ Line2,
971
810
  {
972
811
  name: "overlay:snap-vertical",
973
812
  points: [activeVertical.position, -99999, activeVertical.position, 99999],
@@ -979,7 +818,7 @@ function SnapGuidesOverlay({
979
818
  }
980
819
  ),
981
820
  activeHorizontal && /* @__PURE__ */ jsx4(
982
- Line3,
821
+ Line2,
983
822
  {
984
823
  name: "overlay:snap-horizontal",
985
824
  points: [-99999, activeHorizontal.position, 99999, activeHorizontal.position],
@@ -1080,9 +919,9 @@ function InlineTextEditor({
1080
919
  }
1081
920
 
1082
921
  // src/design-canvas-react/components/Workspace.tsx
1083
- import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
922
+ import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
1084
923
  var SNAP_THRESHOLD_SCREEN_PX = 8;
1085
- var DUPLICATE_OFFSET2 = 10;
924
+ var DUPLICATE_OFFSET = 10;
1086
925
  var ZOOM_FACTOR_WHEEL = 0.998;
1087
926
  var ZOOM_MIN = 0.05;
1088
927
  var ZOOM_MAX = 32;
@@ -1098,7 +937,8 @@ function WorkspaceView({
1098
937
  onExport,
1099
938
  onExportRef,
1100
939
  fitOnMount = true,
1101
- onReady
940
+ onReady,
941
+ render = lightTheme.canvasRender
1102
942
  }) {
1103
943
  const [, setTick] = useState2(0);
1104
944
  const forceRender = useCallback(() => setTick((t) => t + 1), []);
@@ -1195,19 +1035,17 @@ function WorkspaceView({
1195
1035
  return;
1196
1036
  }
1197
1037
  if (e.button !== 0) return;
1198
- const stage = stageRef.current;
1199
1038
  const rect = containerRef.current?.getBoundingClientRect();
1200
- if (!stage || !rect) return;
1039
+ if (!rect) return;
1201
1040
  const screenX = e.clientX - rect.left;
1202
1041
  const screenY = e.clientY - rect.top;
1203
- const hitNode = stage.getIntersection({ x: screenX, y: screenY });
1204
- if (hitNode && !hitNode.name().startsWith("overlay:") && hitNode.name() !== "page-background") {
1042
+ const docPos = zoomPanMath.screenToDocument({ zoom, panX, panY }, screenX, screenY);
1043
+ if (hitTestPoint(activePage, docPos.x, docPos.y) !== null) {
1205
1044
  return;
1206
1045
  }
1207
1046
  e.preventDefault();
1208
1047
  e.currentTarget.setPointerCapture(e.pointerId);
1209
1048
  gestureRef.current = "marquee";
1210
- const docPos = zoomPanMath.screenToDocument({ zoom, panX, panY }, screenX, screenY);
1211
1049
  const m = { active: true, startDocX: docPos.x, startDocY: docPos.y, endDocX: docPos.x, endDocY: docPos.y };
1212
1050
  marqueeRef.current = m;
1213
1051
  setMarquee(m);
@@ -1285,9 +1123,24 @@ function WorkspaceView({
1285
1123
  setActiveSnap(null);
1286
1124
  const origin = dragOriginRef.current.get(elementId);
1287
1125
  if (!origin) return;
1288
- const dx = finalX - origin.x;
1289
- const dy = finalY - origin.y;
1290
1126
  const draggingIds = selectedElementIds.includes(elementId) ? selectedElementIds : [elementId];
1127
+ let snappedX = finalX;
1128
+ let snappedY = finalY;
1129
+ if (snapEnabled) {
1130
+ const snapBounds = { x: finalX, y: finalY, width: origin.width, height: origin.height };
1131
+ const targets = snapEngine.collectTargets(state, draggingIds);
1132
+ if (gridEnabled) {
1133
+ const thresholdDoc = SNAP_THRESHOLD_SCREEN_PX / zoom;
1134
+ const gt = collectGridTargets(snapBounds, gridSize, activePage, thresholdDoc);
1135
+ targets.vertical.push(...gt.vertical);
1136
+ targets.horizontal.push(...gt.horizontal);
1137
+ }
1138
+ const snapResult = snapEngine.apply(snapBounds, targets, SNAP_THRESHOLD_SCREEN_PX, zoom);
1139
+ snappedX = snapResult.x;
1140
+ snappedY = snapResult.y;
1141
+ }
1142
+ const dx = snappedX - origin.x;
1143
+ const dy = snappedY - origin.y;
1291
1144
  if (draggingIds.length === 1) {
1292
1145
  const el = findElement(activePage, elementId)?.element;
1293
1146
  if (!el) return;
@@ -1295,7 +1148,7 @@ function WorkspaceView({
1295
1148
  setAttrsCommand({
1296
1149
  pageId: activePageId,
1297
1150
  elementId,
1298
- attrs: { x: finalX, y: finalY },
1151
+ attrs: { x: snappedX, y: snappedY },
1299
1152
  priorAttrs: { x: el.x, y: el.y }
1300
1153
  })
1301
1154
  );
@@ -1372,6 +1225,7 @@ function WorkspaceView({
1372
1225
  if (editingElementId) return;
1373
1226
  if ((e.key === "Delete" || e.key === "Backspace") && selectedElementIds.length > 0) {
1374
1227
  e.preventDefault();
1228
+ e.stopPropagation();
1375
1229
  for (const id of [...selectedElementIds]) {
1376
1230
  try {
1377
1231
  persist(deleteElementCommand({ document, pageId: activePageId, elementId: id }));
@@ -1383,11 +1237,13 @@ function WorkspaceView({
1383
1237
  }
1384
1238
  if (mod && e.key === "z" && !e.shiftKey) {
1385
1239
  e.preventDefault();
1240
+ e.stopPropagation();
1386
1241
  if (stack.canUndo()) stack.undo();
1387
1242
  return;
1388
1243
  }
1389
1244
  if (mod && (e.key === "z" && e.shiftKey || e.key === "y")) {
1390
1245
  e.preventDefault();
1246
+ e.stopPropagation();
1391
1247
  if (stack.canRedo()) stack.redo();
1392
1248
  return;
1393
1249
  }
@@ -1405,8 +1261,8 @@ function WorkspaceView({
1405
1261
  const clone = {
1406
1262
  ...structuredClone(found.element),
1407
1263
  id: crypto.randomUUID(),
1408
- x: found.element.x + DUPLICATE_OFFSET2,
1409
- y: found.element.y + DUPLICATE_OFFSET2
1264
+ x: found.element.x + DUPLICATE_OFFSET,
1265
+ y: found.element.y + DUPLICATE_OFFSET
1410
1266
  };
1411
1267
  persist(addElementCommand({ pageId: activePageId, element: clone }));
1412
1268
  }
@@ -1442,7 +1298,7 @@ function WorkspaceView({
1442
1298
  }
1443
1299
  nudgeHeldRef.current = { key, ids: selectedElementIds.slice(), origin: origins };
1444
1300
  }
1445
- const { dx, dy } = nudgeDelta2(key, e.shiftKey);
1301
+ const { dx, dy } = nudgeDelta(key, e.shiftKey);
1446
1302
  const entries = [];
1447
1303
  for (const [id, orig] of nudgeHeldRef.current.origin) {
1448
1304
  const found = findElement(activePage, id);
@@ -1518,7 +1374,40 @@ function WorkspaceView({
1518
1374
  const activeVerticalGuide = activeSnap?.activeVertical ?? null;
1519
1375
  const activeHorizontalGuide = activeSnap?.activeHorizontal ?? null;
1520
1376
  const normalizedMarquee = marquee.active ? normalizeMarquee(marquee.startDocX, marquee.startDocY, marquee.endDocX, marquee.endDocY) : null;
1521
- return /* @__PURE__ */ jsxs3(
1377
+ const handlerImplRef = useRef4({
1378
+ onClick: handleElementClick,
1379
+ onDragStart: handleElementDragStart,
1380
+ onDragMove: handleElementDragMove,
1381
+ onDragEnd: handleElementDragEnd,
1382
+ onDoubleClick: handleElementDoubleClick
1383
+ });
1384
+ handlerImplRef.current = {
1385
+ onClick: handleElementClick,
1386
+ onDragStart: handleElementDragStart,
1387
+ onDragMove: handleElementDragMove,
1388
+ onDragEnd: handleElementDragEnd,
1389
+ onDoubleClick: handleElementDoubleClick
1390
+ };
1391
+ const onElementClick = useCallback((id) => handlerImplRef.current.onClick(id), []);
1392
+ const onElementDragStart = useCallback((id) => handlerImplRef.current.onDragStart(id), []);
1393
+ const onElementDragMove = useCallback(
1394
+ (id, dx, dy) => handlerImplRef.current.onDragMove(id, dx, dy),
1395
+ []
1396
+ );
1397
+ const onElementDragEnd = useCallback(
1398
+ (id, x, y) => handlerImplRef.current.onDragEnd(id, x, y),
1399
+ []
1400
+ );
1401
+ const onElementDoubleClick = useCallback((id) => handlerImplRef.current.onDoubleClick(id), []);
1402
+ const visibleElements = useMemo(() => {
1403
+ const pageRect = { x: 0, y: 0, width: activePage.width, height: activePage.height };
1404
+ const pinned = new Set(selectedElementIds);
1405
+ if (editingElementId) pinned.add(editingElementId);
1406
+ return activePage.elements.filter(
1407
+ (el) => pinned.has(el.id) || boundsIntersect(elementAabb(el), pageRect)
1408
+ );
1409
+ }, [activePage.elements, activePage.width, activePage.height, selectedElementIds, editingElementId]);
1410
+ return /* @__PURE__ */ jsxs2(
1522
1411
  "div",
1523
1412
  {
1524
1413
  className: `design-canvas-workspace relative overflow-hidden bg-[var(--canvas-backdrop,#1a1a1a)] outline-none ${className ?? ""}`,
@@ -1532,7 +1421,7 @@ function WorkspaceView({
1532
1421
  onKeyUp: handleKeyUp,
1533
1422
  style: { cursor: spaceHeldRef.current ? "grab" : "default" },
1534
1423
  children: [
1535
- /* @__PURE__ */ jsxs3(
1424
+ /* @__PURE__ */ jsxs2(
1536
1425
  Stage,
1537
1426
  {
1538
1427
  ref: stageRef,
@@ -1547,10 +1436,13 @@ function WorkspaceView({
1547
1436
  pageWidth: activePage.width,
1548
1437
  pageHeight: activePage.height,
1549
1438
  gridSize,
1550
- zoom
1439
+ zoom,
1440
+ panX,
1441
+ panY,
1442
+ color: render.grid
1551
1443
  }
1552
1444
  ),
1553
- /* @__PURE__ */ jsx6(Layer4, { children: /* @__PURE__ */ jsxs3(Group2, { x: panX, y: panY, scaleX: zoom, scaleY: zoom, children: [
1445
+ /* @__PURE__ */ jsx6(Layer4, { children: /* @__PURE__ */ jsxs2(Group3, { x: panX, y: panY, scaleX: zoom, scaleY: zoom, children: [
1554
1446
  /* @__PURE__ */ jsx6(
1555
1447
  Rect2,
1556
1448
  {
@@ -1567,37 +1459,39 @@ function WorkspaceView({
1567
1459
  }
1568
1460
  ),
1569
1461
  /* @__PURE__ */ jsx6(
1570
- Group2,
1462
+ Group3,
1571
1463
  {
1572
1464
  clipX: 0,
1573
1465
  clipY: 0,
1574
1466
  clipWidth: activePage.width,
1575
1467
  clipHeight: activePage.height,
1576
- children: activePage.elements.map((element) => /* @__PURE__ */ jsx6(
1468
+ children: visibleElements.map((element) => /* @__PURE__ */ jsx6(
1577
1469
  ElementNode,
1578
1470
  {
1579
1471
  element,
1580
1472
  isSelected: selectedElementIds.includes(element.id),
1581
1473
  zoom,
1582
- onClick: handleElementClick,
1583
- onDragStart: handleElementDragStart,
1584
- onDragMove: handleElementDragMove,
1585
- onDragEnd: handleElementDragEnd,
1586
- onDoubleClick: handleElementDoubleClick
1474
+ render,
1475
+ onClick: onElementClick,
1476
+ onDragStart: onElementDragStart,
1477
+ onDragMove: onElementDragMove,
1478
+ onDragEnd: onElementDragEnd,
1479
+ onDoubleClick: onElementDoubleClick
1587
1480
  },
1588
1481
  element.id
1589
1482
  ))
1590
1483
  }
1591
1484
  )
1592
1485
  ] }) }),
1593
- (activeVerticalGuide || activeHorizontalGuide) && /* @__PURE__ */ jsx6(Layer4, { children: /* @__PURE__ */ jsx6(Group2, { x: panX, y: panY, scaleX: zoom, scaleY: zoom, children: /* @__PURE__ */ jsx6(
1486
+ (activeVerticalGuide || activeHorizontalGuide) && /* @__PURE__ */ jsx6(Layer4, { children: /* @__PURE__ */ jsx6(Group3, { x: panX, y: panY, scaleX: zoom, scaleY: zoom, children: /* @__PURE__ */ jsx6(
1594
1487
  SnapGuidesOverlay,
1595
1488
  {
1596
1489
  pageWidth: activePage.width,
1597
1490
  pageHeight: activePage.height,
1598
1491
  activeVertical: activeVerticalGuide,
1599
1492
  activeHorizontal: activeHorizontalGuide,
1600
- zoom
1493
+ zoom,
1494
+ render
1601
1495
  }
1602
1496
  ) }) }),
1603
1497
  /* @__PURE__ */ jsx6(
@@ -1608,7 +1502,8 @@ function WorkspaceView({
1608
1502
  selectedElements,
1609
1503
  canWrite,
1610
1504
  onTransformEnd: handleTransformEnd,
1611
- pageId: activePageId
1505
+ pageId: activePageId,
1506
+ render
1612
1507
  }
1613
1508
  )
1614
1509
  ]
@@ -1691,7 +1586,8 @@ function Workspace(props) {
1691
1586
  canWrite: props.canWrite,
1692
1587
  onApplyOperations: props.onApplyOperations,
1693
1588
  onSelectionChange: props.onSelectionChange,
1694
- className: props.className
1589
+ className: props.className,
1590
+ render: props.render
1695
1591
  }
1696
1592
  );
1697
1593
  }
@@ -1852,7 +1748,8 @@ function DesignCanvasEditor(props) {
1852
1748
  onExport: props.onExport,
1853
1749
  onExportRef: ctx.onExportRef,
1854
1750
  fitOnMount: ctx.fitOnMount,
1855
- onReady: ctx.onReady
1751
+ onReady: ctx.onReady,
1752
+ render: ctx.render
1856
1753
  }
1857
1754
  );
1858
1755
  },
@@ -1862,17 +1759,6 @@ function DesignCanvasEditor(props) {
1862
1759
  }
1863
1760
 
1864
1761
  export {
1865
- createSnapEngine,
1866
- collectGridTargets,
1867
- marqueeSelect,
1868
- nudgeDelta,
1869
- DUPLICATE_OFFSET,
1870
- createZoomPanMath,
1871
- isExportHiddenNodeName,
1872
- resolveExportParams,
1873
- identifyTaintedSrc,
1874
- isCrossOriginSrc,
1875
- documentCropToStageCoords,
1876
1762
  exportPageDataUrl,
1877
1763
  exportDocumentJson,
1878
1764
  downloadDataUrl,
@@ -1886,4 +1772,4 @@ export {
1886
1772
  Workspace,
1887
1773
  DesignCanvasEditor
1888
1774
  };
1889
- //# sourceMappingURL=chunk-FSUR2752.js.map
1775
+ //# sourceMappingURL=chunk-7I37CO5D.js.map