@tangle-network/agent-app 0.27.2 → 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 (98) 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/{chunk-AFDROJ64.js → chunk-2QI7XV2T.js} +1 -1
  6. package/dist/chunk-2QI7XV2T.js.map +1 -0
  7. package/dist/{chunk-LUE4HO5C.js → chunk-4IQMTTNE.js} +10 -10
  8. package/dist/chunk-4IQMTTNE.js.map +1 -0
  9. package/dist/{chunk-HABVCJJT.js → chunk-7I37CO5D.js} +271 -404
  10. package/dist/chunk-7I37CO5D.js.map +1 -0
  11. package/dist/{chunk-5RT6KY4G.js → chunk-GQRL6VNU.js} +1 -1
  12. package/dist/chunk-GQRL6VNU.js.map +1 -0
  13. package/dist/{chunk-SCG5JJ4C.js → chunk-HFC4BTWJ.js} +32 -4
  14. package/dist/chunk-HFC4BTWJ.js.map +1 -0
  15. package/dist/{chunk-7QCIYDGC.js → chunk-IOREXWLM.js} +2 -2
  16. package/dist/chunk-J7JNV5BH.js +14 -0
  17. package/dist/chunk-J7JNV5BH.js.map +1 -0
  18. package/dist/{chunk-6UOE5CTA.js → chunk-JT6HHO5P.js} +5 -10
  19. package/dist/chunk-JT6HHO5P.js.map +1 -0
  20. package/dist/{chunk-JZAJE3JL.js → chunk-M2EBUDZ7.js} +19 -5
  21. package/dist/chunk-M2EBUDZ7.js.map +1 -0
  22. package/dist/{chunk-RJ2ZHK5D.js → chunk-S6WS2B32.js} +132 -7
  23. package/dist/chunk-S6WS2B32.js.map +1 -0
  24. package/dist/chunk-SQV7NUEM.js +136 -0
  25. package/dist/chunk-SQV7NUEM.js.map +1 -0
  26. package/dist/chunk-UAKFCMWK.js +1 -0
  27. package/dist/chunk-UAKFCMWK.js.map +1 -0
  28. package/dist/chunk-V5XZKHCL.js +296 -0
  29. package/dist/chunk-V5XZKHCL.js.map +1 -0
  30. package/dist/chunk-XWJXZ6WE.js +604 -0
  31. package/dist/chunk-XWJXZ6WE.js.map +1 -0
  32. package/dist/chunk-Y2SZVW35.js +125 -0
  33. package/dist/chunk-Y2SZVW35.js.map +1 -0
  34. package/dist/{chunk-27LQXQSS.js → chunk-YEWTVDDT.js} +224 -767
  35. package/dist/chunk-YEWTVDDT.js.map +1 -0
  36. package/dist/{chunk-UDXMR3AD.js → chunk-YNALYIQ3.js} +56 -18
  37. package/dist/chunk-YNALYIQ3.js.map +1 -0
  38. package/dist/command-stack-CkjelfK_.d.ts +267 -0
  39. package/dist/design-canvas/drizzle.d.ts +2 -2
  40. package/dist/design-canvas/index.d.ts +7 -6
  41. package/dist/design-canvas/index.js +3 -2
  42. package/dist/design-canvas-react/engine.d.ts +349 -0
  43. package/dist/design-canvas-react/engine.js +86 -0
  44. package/dist/design-canvas-react/engine.js.map +1 -0
  45. package/dist/design-canvas-react/index.d.ts +69 -649
  46. package/dist/design-canvas-react/index.js +84 -173
  47. package/dist/design-canvas-react/index.js.map +1 -1
  48. package/dist/design-canvas-react/lazy.d.ts +6 -0
  49. package/dist/design-canvas-react/lazy.js +9 -0
  50. package/dist/design-canvas-react/lazy.js.map +1 -0
  51. package/dist/{export-presets-Dl5Aa5xj.d.ts → export-presets-DAsU5Wal.d.ts} +2 -143
  52. package/dist/flow-types-Cb_AblZs.d.ts +25 -0
  53. package/dist/index.d.ts +11 -8
  54. package/dist/index.js +21 -8
  55. package/dist/lazy-BHHqS8-j.d.ts +72 -0
  56. package/dist/missions/index.d.ts +12 -0
  57. package/dist/missions/index.js +1 -1
  58. package/dist/{model-BHLN208Z.d.ts → model-BBwyOuWG.d.ts} +3 -1
  59. package/dist/operations-DLIvvD00.d.ts +144 -0
  60. package/dist/platform/index.js +1 -1
  61. package/dist/runtime/index.d.ts +33 -33
  62. package/dist/runtime/index.js +1 -1
  63. package/dist/sequences/index.d.ts +1 -1
  64. package/dist/sequences/index.js +2 -1
  65. package/dist/sequences-react/index.d.ts +15 -1
  66. package/dist/sequences-react/index.js +2 -2
  67. package/dist/{store-CUStmtdH.d.ts → store-CFyxgTlD.d.ts} +1 -1
  68. package/dist/theme/index.d.ts +80 -0
  69. package/dist/theme/index.js +14 -0
  70. package/dist/theme/index.js.map +1 -0
  71. package/dist/theme/tailwind-preset.d.ts +65 -0
  72. package/dist/theme/tailwind-preset.js +33 -0
  73. package/dist/theme/tailwind-preset.js.map +1 -0
  74. package/dist/theme/tokens.css +95 -0
  75. package/dist/trace/index.d.ts +2 -18
  76. package/dist/trace/index.js +2 -2
  77. package/dist/web/index.d.ts +25 -2
  78. package/dist/web/index.js +3 -1
  79. package/dist/web-react/index.d.ts +71 -31
  80. package/dist/web-react/index.js +625 -486
  81. package/dist/web-react/index.js.map +1 -1
  82. package/package.json +28 -3
  83. package/dist/DesignCanvas-73IUJJUP.js +0 -10
  84. package/dist/DesignCanvasEditor-6F2UTOSW.js +0 -9
  85. package/dist/chunk-27LQXQSS.js.map +0 -1
  86. package/dist/chunk-5RT6KY4G.js.map +0 -1
  87. package/dist/chunk-6UOE5CTA.js.map +0 -1
  88. package/dist/chunk-AFDROJ64.js.map +0 -1
  89. package/dist/chunk-HABVCJJT.js.map +0 -1
  90. package/dist/chunk-JZAJE3JL.js.map +0 -1
  91. package/dist/chunk-LUE4HO5C.js.map +0 -1
  92. package/dist/chunk-RJ2ZHK5D.js.map +0 -1
  93. package/dist/chunk-SCG5JJ4C.js.map +0 -1
  94. package/dist/chunk-UDXMR3AD.js.map +0 -1
  95. /package/dist/{DesignCanvas-73IUJJUP.js.map → DesignCanvas-AOIVN3SJ.js.map} +0 -0
  96. /package/dist/{DesignCanvasEditor-6F2UTOSW.js.map → DesignCanvasEditor-TN7CWUJW.js.map} +0 -0
  97. /package/dist/{TimelineEditor-JDG54OBP.js.map → TimelineEditor-Z7C6BFP5.js.map} +0 -0
  98. /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-27LQXQSS.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,247 +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 hitTestPoint(page, x, y) {
322
- return hitTestIn(page.elements, x, y, null);
323
- }
324
- function hitTestIn(elements, x, y, resolveTo) {
325
- for (let i = elements.length - 1; i >= 0; i -= 1) {
326
- const el = elements[i];
327
- if (!el.visible || el.locked) continue;
328
- const aabb = elementAabb(el);
329
- if (!pointInBounds(aabb, x, y)) continue;
330
- if (el.kind === "group") {
331
- const child = hitTestIn(el.children, x, y, el.id);
332
- return child ?? el.id;
333
- }
334
- return resolveTo ?? el.id;
335
- }
336
- return null;
337
- }
338
- function pointInBounds(b, x, y) {
339
- return x >= b.x && x <= b.x + b.width && y >= b.y && y <= b.y + b.height;
340
- }
341
- function marqueeSelect(page, rect, opts = {}) {
342
- assertValidBounds(rect, "marqueeSelect rect");
343
- const result = [];
344
- collectMarqueeIds(page.elements, rect, opts.requireFullContainment ?? false, result);
345
- return result;
346
- }
347
- function collectMarqueeIds(elements, rect, requireContainment, result) {
348
- for (const el of elements) {
349
- if (!el.visible || el.locked) continue;
350
- const aabb = elementAabb(el);
351
- if (el.kind === "group") {
352
- if (boundsIntersect(rect, aabb)) {
353
- collectMarqueeIds(el.children, rect, requireContainment, result);
354
- }
355
- continue;
356
- }
357
- const hit = requireContainment ? boundsContain(rect, aabb) : boundsIntersect(rect, aabb);
358
- if (hit) result.push(el.id);
359
- }
360
- }
361
- function boundsContain(outer, inner) {
362
- 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;
363
- }
364
- var NUDGE_NORMAL_PX = 1;
365
- var NUDGE_SHIFT_PX = 10;
366
- function nudgeDelta(key, shift) {
367
- const step = shift ? NUDGE_SHIFT_PX : NUDGE_NORMAL_PX;
368
- switch (key) {
369
- case "ArrowLeft":
370
- return { dx: -step, dy: 0 };
371
- case "ArrowRight":
372
- return { dx: step, dy: 0 };
373
- case "ArrowUp":
374
- return { dx: 0, dy: -step };
375
- case "ArrowDown":
376
- return { dx: 0, dy: step };
377
- }
378
- }
379
- var DUPLICATE_OFFSET = { dx: 10, dy: 10 };
380
- function assertValidBounds(bounds, label) {
381
- if (!Number.isFinite(bounds.x) || !Number.isFinite(bounds.y) || !Number.isFinite(bounds.width) || !Number.isFinite(bounds.height)) {
382
- throw new Error(`${label}: all fields must be finite numbers`);
383
- }
384
- }
385
-
386
126
  // src/design-canvas-react/components/ElementNode.tsx
387
- import { useEffect, useRef, useState } from "react";
127
+ import { memo, useEffect, useRef, useState } from "react";
388
128
  import { Group, Rect, Ellipse, Line, Text, Image as KonvaImage } from "react-konva";
389
129
 
390
130
  // src/design-canvas-react/components/transform-math.ts
@@ -471,7 +211,7 @@ function snapRotation(angleDeg, thresholdDeg = 5) {
471
211
  }
472
212
  return bestDist <= thresholdDeg ? best : normalized;
473
213
  }
474
- function nudgeDelta2(key, shift) {
214
+ function nudgeDelta(key, shift) {
475
215
  const step = shift ? 10 : 1;
476
216
  switch (key) {
477
217
  case "ArrowLeft":
@@ -517,7 +257,7 @@ function useImage(src) {
517
257
  }, [src]);
518
258
  return imageCache.get(src) ?? null;
519
259
  }
520
- function ElementNode(props) {
260
+ function ElementNodeImpl(props) {
521
261
  const { element } = props;
522
262
  if (!element.visible) return null;
523
263
  switch (element.kind) {
@@ -537,33 +277,55 @@ function ElementNode(props) {
537
277
  return /* @__PURE__ */ jsx(GroupNode, { ...props, element });
538
278
  }
539
279
  }
280
+ var ElementNode = memo(ElementNodeImpl);
540
281
  function useDragBindings(props, originX, originY) {
541
282
  const { element, onClick, onDragStart, onDragMove, onDragEnd, onDoubleClick } = props;
542
283
  const isDraggable = !element.locked && !!onDragEnd;
543
284
  const originRef = useRef({ x: originX, y: originY });
544
285
  originRef.current = { x: originX, y: originY };
286
+ const [dragging, setDragging] = useState(false);
545
287
  return {
546
288
  draggable: isDraggable,
547
289
  // locked elements still receive click for selection; listening must be true.
548
290
  listening: true,
291
+ dragging,
549
292
  onDragStart: isDraggable ? () => {
293
+ setDragging(true);
550
294
  onDragStart?.(element.id);
551
295
  } : void 0,
552
296
  onDragMove: isDraggable ? (e) => {
553
297
  onDragMove?.(element.id, e.target.x() - originRef.current.x, e.target.y() - originRef.current.y);
554
298
  } : void 0,
555
299
  onDragEnd: isDraggable ? (e) => {
300
+ setDragging(false);
556
301
  onDragEnd?.(element.id, e.target.x(), e.target.y());
557
302
  } : void 0,
558
303
  onClick: () => onClick?.(element.id),
559
304
  onDblClick: () => onDoubleClick?.(element.id)
560
305
  };
561
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
+ }
562
321
  function RectNode({ element, ...rest }) {
563
- 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);
564
325
  return /* @__PURE__ */ jsx(
565
326
  Rect,
566
327
  {
328
+ ref,
567
329
  name: element.id,
568
330
  x: element.x,
569
331
  y: element.y,
@@ -581,10 +343,13 @@ function RectNode({ element, ...rest }) {
581
343
  }
582
344
  function EllipseNode({ element, ...rest }) {
583
345
  const { x: cx, y: cy, radiusX, radiusY } = ellipseCenterFromTopLeft(element);
584
- 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);
585
349
  return /* @__PURE__ */ jsx(
586
350
  Ellipse,
587
351
  {
352
+ ref,
588
353
  name: element.id,
589
354
  x: cx,
590
355
  y: cy,
@@ -600,10 +365,13 @@ function EllipseNode({ element, ...rest }) {
600
365
  );
601
366
  }
602
367
  function LineNode({ element, ...rest }) {
603
- 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);
604
371
  return /* @__PURE__ */ jsx(
605
372
  Line,
606
373
  {
374
+ ref,
607
375
  name: element.id,
608
376
  x: element.x,
609
377
  y: element.y,
@@ -624,11 +392,14 @@ var FONT_STYLE_MAP = {
624
392
  "bold italic": { fontStyle: "italic", fontVariant: "normal", fontWeight: "bold" }
625
393
  };
626
394
  function TextNode({ element, ...rest }) {
627
- 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);
628
398
  const { fontStyle, fontWeight } = FONT_STYLE_MAP[element.fontStyle];
629
399
  return /* @__PURE__ */ jsx(
630
400
  Text,
631
401
  {
402
+ ref,
632
403
  name: element.id,
633
404
  x: element.x,
634
405
  y: element.y,
@@ -647,15 +418,24 @@ function TextNode({ element, ...rest }) {
647
418
  }
648
419
  );
649
420
  }
650
- var BROKEN_FILL = "#e5e7eb";
651
- var BROKEN_STROKE = "#9ca3af";
652
421
  function ImageNode({ element, ...rest }) {
422
+ const render = rest.render ?? lightTheme.canvasRender;
653
423
  const img = useImage(element.src);
654
- 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
+ );
655
434
  if (!img || !img.complete || img.naturalWidth === 0) {
656
435
  return /* @__PURE__ */ jsx(
657
436
  Rect,
658
437
  {
438
+ ref,
659
439
  name: element.id,
660
440
  x: element.x,
661
441
  y: element.y,
@@ -663,8 +443,8 @@ function ImageNode({ element, ...rest }) {
663
443
  height: element.height,
664
444
  rotation: element.rotation,
665
445
  opacity: element.opacity,
666
- fill: BROKEN_FILL,
667
- stroke: BROKEN_STROKE,
446
+ fill: render.brokenFill,
447
+ stroke: render.brokenStroke,
668
448
  strokeWidth: 1,
669
449
  ...drag
670
450
  }
@@ -699,6 +479,7 @@ function ImageNode({ element, ...rest }) {
699
479
  return /* @__PURE__ */ jsx(
700
480
  KonvaImage,
701
481
  {
482
+ ref,
702
483
  name: element.id,
703
484
  x: element.x,
704
485
  y: element.y,
@@ -713,12 +494,23 @@ function ImageNode({ element, ...rest }) {
713
494
  );
714
495
  }
715
496
  function VideoNode({ element, ...rest }) {
497
+ const render = rest.render ?? lightTheme.canvasRender;
716
498
  const img = useImage(element.posterSrc ?? "");
717
- 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
+ );
718
509
  if (!element.posterSrc || !img || !img.complete || img.naturalWidth === 0) {
719
510
  return /* @__PURE__ */ jsx(
720
511
  Rect,
721
512
  {
513
+ ref,
722
514
  name: element.id,
723
515
  x: element.x,
724
516
  y: element.y,
@@ -726,8 +518,8 @@ function VideoNode({ element, ...rest }) {
726
518
  height: element.height,
727
519
  rotation: element.rotation,
728
520
  opacity: element.opacity,
729
- fill: "#1f2937",
730
- stroke: "#374151",
521
+ fill: render.placeholderFill,
522
+ stroke: render.placeholderStroke,
731
523
  strokeWidth: 1,
732
524
  ...drag
733
525
  }
@@ -736,6 +528,7 @@ function VideoNode({ element, ...rest }) {
736
528
  return /* @__PURE__ */ jsx(
737
529
  KonvaImage,
738
530
  {
531
+ ref,
739
532
  name: element.id,
740
533
  x: element.x,
741
534
  y: element.y,
@@ -749,7 +542,8 @@ function VideoNode({ element, ...rest }) {
749
542
  );
750
543
  }
751
544
  function GroupNode({ element, ...rest }) {
752
- const drag = useDragBindings({ element, ...rest }, element.x, element.y);
545
+ const { dragging: _dragging, ...drag } = useDragBindings({ element, ...rest }, element.x, element.y);
546
+ void _dragging;
753
547
  return /* @__PURE__ */ jsx(
754
548
  Group,
755
549
  {
@@ -775,7 +569,8 @@ function SelectionLayer({
775
569
  selectedElements,
776
570
  canWrite,
777
571
  onTransformEnd,
778
- pageId
572
+ pageId,
573
+ render = lightTheme.canvasRender
779
574
  }) {
780
575
  const trRef = useRef2(null);
781
576
  useEffect2(() => {
@@ -808,22 +603,35 @@ function SelectionLayer({
808
603
  if (entries.length > 0) onTransformEnd(entries);
809
604
  }
810
605
  const multiSelect = selectedIds.length > 1;
811
- return /* @__PURE__ */ jsx2(Layer, { name: "overlay:selection", listening: false, children: /* @__PURE__ */ jsx2(
812
- Transformer,
813
- {
814
- ref: trRef,
815
- name: "overlay:transformer",
816
- keepRatio: multiSelect,
817
- rotationSnaps: [0, 45, 90, 135, 180, 225, 270, 315],
818
- rotationSnapTolerance: 5,
819
- boundBoxFunc: (oldBox, newBox) => {
820
- if (newBox.width < MIN_SIZE || newBox.height < MIN_SIZE) return oldBox;
821
- return newBox;
822
- },
823
- listening: canWrite,
824
- onTransformEnd: canWrite ? handleTransformEnd : void 0
825
- }
826
- ) });
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
+ );
827
635
  }
828
636
  function elementToPriorAttrs(element) {
829
637
  const base = {
@@ -916,78 +724,89 @@ function bakeNodeToAttrs(node, element) {
916
724
  }
917
725
 
918
726
  // src/design-canvas-react/components/GridLayer.tsx
919
- import { Layer as Layer2, Line as Line2 } from "react-konva";
920
- 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
+ }
921
741
  function GridLayer({
922
742
  pageWidth,
923
743
  pageHeight,
924
744
  gridSize,
925
745
  zoom,
746
+ panX,
747
+ panY,
926
748
  color = "#c0c0c0",
927
749
  opacity = 0.5
928
750
  }) {
929
751
  if (!gridVisible(gridSize, zoom, 4)) return null;
930
- const verticals = [];
931
- for (let x = gridSize; x < pageWidth; x += gridSize) {
932
- verticals.push(x);
933
- }
934
- const horizontals = [];
935
- for (let y = gridSize; y < pageHeight; y += gridSize) {
936
- horizontals.push(y);
937
- }
938
- return /* @__PURE__ */ jsxs(Layer2, { name: "overlay:grid", listening: false, children: [
939
- verticals.map((x) => /* @__PURE__ */ jsx3(
940
- Line2,
941
- {
942
- name: "overlay:grid-line",
943
- points: [x, 0, x, pageHeight],
944
- stroke: color,
945
- strokeWidth: 1 / zoom,
946
- opacity,
947
- listening: false,
948
- perfectDrawEnabled: false
949
- },
950
- `v-${x}`
951
- )),
952
- horizontals.map((y) => /* @__PURE__ */ jsx3(
953
- Line2,
954
- {
955
- name: "overlay:grid-line",
956
- points: [0, y, pageWidth, y],
957
- stroke: color,
958
- strokeWidth: 1 / zoom,
959
- opacity,
960
- listening: false,
961
- perfectDrawEnabled: false
962
- },
963
- `h-${y}`
964
- ))
965
- ] });
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
+ ) }) });
966
781
  }
967
782
 
968
783
  // src/design-canvas-react/components/SnapGuidesOverlay.tsx
969
- import { Layer as Layer3, Line as Line3 } from "react-konva";
970
- import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
971
- var KIND_COLOR = {
972
- "grid": "#a0a0a0",
973
- "guide": "#3b82f6",
974
- "page-edge": "#f59e0b",
975
- "page-center": "#f59e0b",
976
- "element-edge": "#f43f5e",
977
- "element-center": "#f43f5e"
978
- };
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
+ }
979
796
  function SnapGuidesOverlay({
980
797
  pageWidth,
981
798
  pageHeight,
982
799
  activeVertical,
983
800
  activeHorizontal,
984
- zoom
801
+ zoom,
802
+ render = lightTheme.canvasRender
985
803
  }) {
986
804
  if (!activeVertical && !activeHorizontal) return null;
805
+ const KIND_COLOR = kindColors(render);
987
806
  const strokeWidth = 1 / zoom;
988
- return /* @__PURE__ */ jsxs2(Layer3, { name: "overlay:snap", listening: false, children: [
807
+ return /* @__PURE__ */ jsxs(Layer3, { name: "overlay:snap", listening: false, children: [
989
808
  activeVertical && /* @__PURE__ */ jsx4(
990
- Line3,
809
+ Line2,
991
810
  {
992
811
  name: "overlay:snap-vertical",
993
812
  points: [activeVertical.position, -99999, activeVertical.position, 99999],
@@ -999,7 +818,7 @@ function SnapGuidesOverlay({
999
818
  }
1000
819
  ),
1001
820
  activeHorizontal && /* @__PURE__ */ jsx4(
1002
- Line3,
821
+ Line2,
1003
822
  {
1004
823
  name: "overlay:snap-horizontal",
1005
824
  points: [-99999, activeHorizontal.position, 99999, activeHorizontal.position],
@@ -1100,9 +919,9 @@ function InlineTextEditor({
1100
919
  }
1101
920
 
1102
921
  // src/design-canvas-react/components/Workspace.tsx
1103
- import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
922
+ import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
1104
923
  var SNAP_THRESHOLD_SCREEN_PX = 8;
1105
- var DUPLICATE_OFFSET2 = 10;
924
+ var DUPLICATE_OFFSET = 10;
1106
925
  var ZOOM_FACTOR_WHEEL = 0.998;
1107
926
  var ZOOM_MIN = 0.05;
1108
927
  var ZOOM_MAX = 32;
@@ -1118,7 +937,8 @@ function WorkspaceView({
1118
937
  onExport,
1119
938
  onExportRef,
1120
939
  fitOnMount = true,
1121
- onReady
940
+ onReady,
941
+ render = lightTheme.canvasRender
1122
942
  }) {
1123
943
  const [, setTick] = useState2(0);
1124
944
  const forceRender = useCallback(() => setTick((t) => t + 1), []);
@@ -1303,9 +1123,24 @@ function WorkspaceView({
1303
1123
  setActiveSnap(null);
1304
1124
  const origin = dragOriginRef.current.get(elementId);
1305
1125
  if (!origin) return;
1306
- const dx = finalX - origin.x;
1307
- const dy = finalY - origin.y;
1308
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;
1309
1144
  if (draggingIds.length === 1) {
1310
1145
  const el = findElement(activePage, elementId)?.element;
1311
1146
  if (!el) return;
@@ -1313,7 +1148,7 @@ function WorkspaceView({
1313
1148
  setAttrsCommand({
1314
1149
  pageId: activePageId,
1315
1150
  elementId,
1316
- attrs: { x: finalX, y: finalY },
1151
+ attrs: { x: snappedX, y: snappedY },
1317
1152
  priorAttrs: { x: el.x, y: el.y }
1318
1153
  })
1319
1154
  );
@@ -1390,6 +1225,7 @@ function WorkspaceView({
1390
1225
  if (editingElementId) return;
1391
1226
  if ((e.key === "Delete" || e.key === "Backspace") && selectedElementIds.length > 0) {
1392
1227
  e.preventDefault();
1228
+ e.stopPropagation();
1393
1229
  for (const id of [...selectedElementIds]) {
1394
1230
  try {
1395
1231
  persist(deleteElementCommand({ document, pageId: activePageId, elementId: id }));
@@ -1401,11 +1237,13 @@ function WorkspaceView({
1401
1237
  }
1402
1238
  if (mod && e.key === "z" && !e.shiftKey) {
1403
1239
  e.preventDefault();
1240
+ e.stopPropagation();
1404
1241
  if (stack.canUndo()) stack.undo();
1405
1242
  return;
1406
1243
  }
1407
1244
  if (mod && (e.key === "z" && e.shiftKey || e.key === "y")) {
1408
1245
  e.preventDefault();
1246
+ e.stopPropagation();
1409
1247
  if (stack.canRedo()) stack.redo();
1410
1248
  return;
1411
1249
  }
@@ -1423,8 +1261,8 @@ function WorkspaceView({
1423
1261
  const clone = {
1424
1262
  ...structuredClone(found.element),
1425
1263
  id: crypto.randomUUID(),
1426
- x: found.element.x + DUPLICATE_OFFSET2,
1427
- y: found.element.y + DUPLICATE_OFFSET2
1264
+ x: found.element.x + DUPLICATE_OFFSET,
1265
+ y: found.element.y + DUPLICATE_OFFSET
1428
1266
  };
1429
1267
  persist(addElementCommand({ pageId: activePageId, element: clone }));
1430
1268
  }
@@ -1460,7 +1298,7 @@ function WorkspaceView({
1460
1298
  }
1461
1299
  nudgeHeldRef.current = { key, ids: selectedElementIds.slice(), origin: origins };
1462
1300
  }
1463
- const { dx, dy } = nudgeDelta2(key, e.shiftKey);
1301
+ const { dx, dy } = nudgeDelta(key, e.shiftKey);
1464
1302
  const entries = [];
1465
1303
  for (const [id, orig] of nudgeHeldRef.current.origin) {
1466
1304
  const found = findElement(activePage, id);
@@ -1536,7 +1374,40 @@ function WorkspaceView({
1536
1374
  const activeVerticalGuide = activeSnap?.activeVertical ?? null;
1537
1375
  const activeHorizontalGuide = activeSnap?.activeHorizontal ?? null;
1538
1376
  const normalizedMarquee = marquee.active ? normalizeMarquee(marquee.startDocX, marquee.startDocY, marquee.endDocX, marquee.endDocY) : null;
1539
- 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(
1540
1411
  "div",
1541
1412
  {
1542
1413
  className: `design-canvas-workspace relative overflow-hidden bg-[var(--canvas-backdrop,#1a1a1a)] outline-none ${className ?? ""}`,
@@ -1550,7 +1421,7 @@ function WorkspaceView({
1550
1421
  onKeyUp: handleKeyUp,
1551
1422
  style: { cursor: spaceHeldRef.current ? "grab" : "default" },
1552
1423
  children: [
1553
- /* @__PURE__ */ jsxs3(
1424
+ /* @__PURE__ */ jsxs2(
1554
1425
  Stage,
1555
1426
  {
1556
1427
  ref: stageRef,
@@ -1565,10 +1436,13 @@ function WorkspaceView({
1565
1436
  pageWidth: activePage.width,
1566
1437
  pageHeight: activePage.height,
1567
1438
  gridSize,
1568
- zoom
1439
+ zoom,
1440
+ panX,
1441
+ panY,
1442
+ color: render.grid
1569
1443
  }
1570
1444
  ),
1571
- /* @__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: [
1572
1446
  /* @__PURE__ */ jsx6(
1573
1447
  Rect2,
1574
1448
  {
@@ -1585,37 +1459,39 @@ function WorkspaceView({
1585
1459
  }
1586
1460
  ),
1587
1461
  /* @__PURE__ */ jsx6(
1588
- Group2,
1462
+ Group3,
1589
1463
  {
1590
1464
  clipX: 0,
1591
1465
  clipY: 0,
1592
1466
  clipWidth: activePage.width,
1593
1467
  clipHeight: activePage.height,
1594
- children: activePage.elements.map((element) => /* @__PURE__ */ jsx6(
1468
+ children: visibleElements.map((element) => /* @__PURE__ */ jsx6(
1595
1469
  ElementNode,
1596
1470
  {
1597
1471
  element,
1598
1472
  isSelected: selectedElementIds.includes(element.id),
1599
1473
  zoom,
1600
- onClick: handleElementClick,
1601
- onDragStart: handleElementDragStart,
1602
- onDragMove: handleElementDragMove,
1603
- onDragEnd: handleElementDragEnd,
1604
- onDoubleClick: handleElementDoubleClick
1474
+ render,
1475
+ onClick: onElementClick,
1476
+ onDragStart: onElementDragStart,
1477
+ onDragMove: onElementDragMove,
1478
+ onDragEnd: onElementDragEnd,
1479
+ onDoubleClick: onElementDoubleClick
1605
1480
  },
1606
1481
  element.id
1607
1482
  ))
1608
1483
  }
1609
1484
  )
1610
1485
  ] }) }),
1611
- (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(
1612
1487
  SnapGuidesOverlay,
1613
1488
  {
1614
1489
  pageWidth: activePage.width,
1615
1490
  pageHeight: activePage.height,
1616
1491
  activeVertical: activeVerticalGuide,
1617
1492
  activeHorizontal: activeHorizontalGuide,
1618
- zoom
1493
+ zoom,
1494
+ render
1619
1495
  }
1620
1496
  ) }) }),
1621
1497
  /* @__PURE__ */ jsx6(
@@ -1626,7 +1502,8 @@ function WorkspaceView({
1626
1502
  selectedElements,
1627
1503
  canWrite,
1628
1504
  onTransformEnd: handleTransformEnd,
1629
- pageId: activePageId
1505
+ pageId: activePageId,
1506
+ render
1630
1507
  }
1631
1508
  )
1632
1509
  ]
@@ -1709,7 +1586,8 @@ function Workspace(props) {
1709
1586
  canWrite: props.canWrite,
1710
1587
  onApplyOperations: props.onApplyOperations,
1711
1588
  onSelectionChange: props.onSelectionChange,
1712
- className: props.className
1589
+ className: props.className,
1590
+ render: props.render
1713
1591
  }
1714
1592
  );
1715
1593
  }
@@ -1870,7 +1748,8 @@ function DesignCanvasEditor(props) {
1870
1748
  onExport: props.onExport,
1871
1749
  onExportRef: ctx.onExportRef,
1872
1750
  fitOnMount: ctx.fitOnMount,
1873
- onReady: ctx.onReady
1751
+ onReady: ctx.onReady,
1752
+ render: ctx.render
1874
1753
  }
1875
1754
  );
1876
1755
  },
@@ -1880,18 +1759,6 @@ function DesignCanvasEditor(props) {
1880
1759
  }
1881
1760
 
1882
1761
  export {
1883
- createSnapEngine,
1884
- collectGridTargets,
1885
- hitTestPoint,
1886
- marqueeSelect,
1887
- nudgeDelta,
1888
- DUPLICATE_OFFSET,
1889
- createZoomPanMath,
1890
- isExportHiddenNodeName,
1891
- resolveExportParams,
1892
- identifyTaintedSrc,
1893
- isCrossOriginSrc,
1894
- documentCropToStageCoords,
1895
1762
  exportPageDataUrl,
1896
1763
  exportDocumentJson,
1897
1764
  downloadDataUrl,
@@ -1905,4 +1772,4 @@ export {
1905
1772
  Workspace,
1906
1773
  DesignCanvasEditor
1907
1774
  };
1908
- //# sourceMappingURL=chunk-HABVCJJT.js.map
1775
+ //# sourceMappingURL=chunk-7I37CO5D.js.map