@bwp-web/canvas 0.4.1 → 0.4.3

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 (37) hide show
  1. package/dist/Canvas/Canvas.d.ts +11 -1
  2. package/dist/Canvas/Canvas.d.ts.map +1 -1
  3. package/dist/background.d.ts +15 -3
  4. package/dist/background.d.ts.map +1 -1
  5. package/dist/hooks/index.d.ts +8 -0
  6. package/dist/hooks/index.d.ts.map +1 -1
  7. package/dist/hooks/shared.d.ts +3 -2
  8. package/dist/hooks/shared.d.ts.map +1 -1
  9. package/dist/hooks/useCanvasClick.d.ts +27 -0
  10. package/dist/hooks/useCanvasClick.d.ts.map +1 -0
  11. package/dist/hooks/useCanvasEvents.d.ts +35 -0
  12. package/dist/hooks/useCanvasEvents.d.ts.map +1 -0
  13. package/dist/hooks/useCanvasTooltip.d.ts +45 -0
  14. package/dist/hooks/useCanvasTooltip.d.ts.map +1 -0
  15. package/dist/hooks/useEditCanvas.d.ts +18 -1
  16. package/dist/hooks/useEditCanvas.d.ts.map +1 -1
  17. package/dist/hooks/useObjectOverlay.d.ts +49 -0
  18. package/dist/hooks/useObjectOverlay.d.ts.map +1 -0
  19. package/dist/hooks/useViewCanvas.d.ts +3 -1
  20. package/dist/hooks/useViewCanvas.d.ts.map +1 -1
  21. package/dist/index.cjs +471 -102
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.ts +13 -4
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +399 -32
  26. package/dist/index.js.map +1 -1
  27. package/dist/interactions/dragToCreate.d.ts +2 -0
  28. package/dist/interactions/dragToCreate.d.ts.map +1 -1
  29. package/dist/interactions/drawToCreate.d.ts +7 -1
  30. package/dist/interactions/drawToCreate.d.ts.map +1 -1
  31. package/dist/serialization.d.ts +12 -1
  32. package/dist/serialization.d.ts.map +1 -1
  33. package/dist/types.d.ts +5 -0
  34. package/dist/types.d.ts.map +1 -1
  35. package/dist/viewport.d.ts +12 -1
  36. package/dist/viewport.d.ts.map +1 -1
  37. package/package.json +6 -5
package/dist/index.cjs CHANGED
@@ -27,6 +27,12 @@ __export(index_exports, {
27
27
  DEFAULT_DRAG_SHAPE_STYLE: () => DEFAULT_DRAG_SHAPE_STYLE,
28
28
  DEFAULT_GUIDELINE_SHAPE_STYLE: () => DEFAULT_GUIDELINE_SHAPE_STYLE,
29
29
  DEFAULT_SHAPE_STYLE: () => DEFAULT_SHAPE_STYLE,
30
+ FabricCanvas: () => import_fabric20.Canvas,
31
+ FabricImage: () => import_fabric20.FabricImage,
32
+ FabricObject: () => import_fabric20.FabricObject,
33
+ Point: () => import_fabric20.Point,
34
+ Polygon: () => import_fabric20.Polygon,
35
+ Rect: () => import_fabric20.Rect,
30
36
  createCircle: () => createCircle,
31
37
  createCircleAtPoint: () => createCircleAtPoint,
32
38
  createPolygon: () => createPolygon,
@@ -51,6 +57,7 @@ __export(index_exports, {
51
57
  fitViewportToBackground: () => fitViewportToBackground,
52
58
  getBackgroundInverted: () => getBackgroundInverted,
53
59
  getBackgroundOpacity: () => getBackgroundOpacity,
60
+ getBackgroundSrc: () => getBackgroundSrc,
54
61
  getBaseStrokeWidth: () => getBaseStrokeWidth,
55
62
  getSnapPoints: () => getSnapPoints,
56
63
  loadCanvas: () => loadCanvas,
@@ -62,13 +69,21 @@ __export(index_exports, {
62
69
  setBackgroundInverted: () => setBackgroundInverted,
63
70
  setBackgroundOpacity: () => setBackgroundOpacity,
64
71
  snapCursorPoint: () => snapCursorPoint,
72
+ useCanvasClick: () => useCanvasClick,
73
+ useCanvasEvents: () => useCanvasEvents,
74
+ useCanvasTooltip: () => useCanvasTooltip,
65
75
  useEditCanvas: () => useEditCanvas,
66
- useViewCanvas: () => useViewCanvas
76
+ useObjectOverlay: () => useObjectOverlay,
77
+ useViewCanvas: () => useViewCanvas,
78
+ util: () => import_fabric20.util
67
79
  });
68
80
  module.exports = __toCommonJS(index_exports);
69
81
 
70
- // src/Canvas/Canvas.tsx
82
+ // src/fabricAugmentation.ts
71
83
  var import_fabric = require("fabric");
84
+
85
+ // src/Canvas/Canvas.tsx
86
+ var import_fabric2 = require("fabric");
72
87
  var import_react = require("react");
73
88
 
74
89
  // src/keyboard.ts
@@ -95,35 +110,64 @@ function enableKeyboardShortcuts(canvas) {
95
110
  // src/Canvas/Canvas.tsx
96
111
  var import_jsx_runtime = require("react/jsx-runtime");
97
112
  function Canvas({
98
- width = 800,
99
- height = 600,
113
+ width,
114
+ height,
100
115
  className,
101
116
  style,
102
117
  onReady
103
118
  }) {
104
119
  const canvasRef = (0, import_react.useRef)(null);
120
+ const wrapperRef = (0, import_react.useRef)(null);
121
+ const isFixedSize = width !== void 0 && height !== void 0;
105
122
  (0, import_react.useEffect)(() => {
106
123
  const el = canvasRef.current;
107
- if (!el) {
108
- return;
109
- }
110
- const fabricCanvas = new import_fabric.Canvas(el, { width, height });
124
+ const wrapper = wrapperRef.current;
125
+ if (!el || !wrapper) return;
126
+ const initialWidth = isFixedSize ? width : wrapper.clientWidth || 800;
127
+ const initialHeight = isFixedSize ? height : wrapper.clientHeight || 600;
128
+ const fabricCanvas = new import_fabric2.Canvas(el, {
129
+ width: initialWidth,
130
+ height: initialHeight
131
+ });
111
132
  onReady?.(fabricCanvas);
112
133
  const cleanupShortcuts = enableKeyboardShortcuts(fabricCanvas);
134
+ let observer;
135
+ let rafId = 0;
136
+ if (!isFixedSize) {
137
+ let currentWidth = initialWidth;
138
+ let currentHeight = initialHeight;
139
+ observer = new ResizeObserver((entries) => {
140
+ cancelAnimationFrame(rafId);
141
+ rafId = requestAnimationFrame(() => {
142
+ const entry = entries[0];
143
+ if (!entry) return;
144
+ const { width: newWidth, height: newHeight } = entry.contentRect;
145
+ if (newWidth > 0 && newHeight > 0 && (newWidth !== currentWidth || newHeight !== currentHeight)) {
146
+ currentWidth = newWidth;
147
+ currentHeight = newHeight;
148
+ fabricCanvas.setDimensions({ width: newWidth, height: newHeight });
149
+ }
150
+ });
151
+ });
152
+ observer.observe(wrapper);
153
+ }
113
154
  return () => {
155
+ cancelAnimationFrame(rafId);
156
+ observer?.disconnect();
114
157
  cleanupShortcuts();
115
158
  fabricCanvas.dispose();
116
159
  };
117
160
  }, []);
118
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, style, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("canvas", { ref: canvasRef }) });
161
+ const wrapperStyle = isFixedSize ? { ...style } : { width: "100%", height: "100%", ...style };
162
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: wrapperRef, className, style: wrapperStyle, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("canvas", { ref: canvasRef }) });
119
163
  }
120
164
 
121
165
  // src/hooks/useEditCanvas.ts
122
166
  var import_react2 = require("react");
123
- var import_fabric16 = require("fabric");
167
+ var import_fabric17 = require("fabric");
124
168
 
125
169
  // src/viewport.ts
126
- var import_fabric2 = require("fabric");
170
+ var import_fabric3 = require("fabric");
127
171
 
128
172
  // src/constants.ts
129
173
  var DEFAULT_MIN_ZOOM = 0.2;
@@ -158,7 +202,7 @@ function touchDistance(touches) {
158
202
  }
159
203
  function touchMidpoint(touches, el) {
160
204
  const rect = el.getBoundingClientRect();
161
- return new import_fabric2.Point(
205
+ return new import_fabric3.Point(
162
206
  (touches[0].clientX + touches[1].clientX) / 2 - rect.left,
163
207
  (touches[0].clientY + touches[1].clientY) / 2 - rect.top
164
208
  );
@@ -173,7 +217,7 @@ function setupWheelZoom(canvas, bounds, zoomFactor, isEnabled) {
173
217
  let zoom = canvas.getZoom();
174
218
  zoom = delta < 0 ? zoom * zoomFactor : zoom / zoomFactor;
175
219
  zoom = Math.min(Math.max(zoom, bounds.minZoom), bounds.maxZoom);
176
- canvas.zoomToPoint(new import_fabric2.Point(e.offsetX, e.offsetY), zoom);
220
+ canvas.zoomToPoint(new import_fabric3.Point(e.offsetX, e.offsetY), zoom);
177
221
  };
178
222
  canvas.on("mouse:wheel", handleWheel);
179
223
  return handleWheel;
@@ -211,7 +255,7 @@ function setupMousePan(canvas, getMode, isEnabled) {
211
255
  const dy = pos.y - lastPanY;
212
256
  lastPanX = pos.x;
213
257
  lastPanY = pos.y;
214
- canvas.relativePan(new import_fabric2.Point(dx, dy));
258
+ canvas.relativePan(new import_fabric3.Point(dx, dy));
215
259
  canvas.setCursor("grab");
216
260
  };
217
261
  const handleMouseUp = () => {
@@ -314,17 +358,65 @@ function enablePanAndZoom(canvas, options) {
314
358
  zoomIn(step = DEFAULT_ZOOM_STEP) {
315
359
  const z = Math.min(canvas.getZoom() + step, bounds.maxZoom);
316
360
  canvas.zoomToPoint(
317
- new import_fabric2.Point(canvas.getWidth() / 2, canvas.getHeight() / 2),
361
+ new import_fabric3.Point(canvas.getWidth() / 2, canvas.getHeight() / 2),
318
362
  z
319
363
  );
320
364
  },
321
365
  zoomOut(step = DEFAULT_ZOOM_STEP) {
322
366
  const z = Math.max(canvas.getZoom() - step, bounds.minZoom);
323
367
  canvas.zoomToPoint(
324
- new import_fabric2.Point(canvas.getWidth() / 2, canvas.getHeight() / 2),
368
+ new import_fabric3.Point(canvas.getWidth() / 2, canvas.getHeight() / 2),
325
369
  z
326
370
  );
327
371
  },
372
+ panToObject(object, panOpts) {
373
+ const zoom = canvas.getZoom();
374
+ const objectCenter = object.getCenterPoint();
375
+ const canvasCenterX = canvas.getWidth() / 2;
376
+ const canvasCenterY = canvas.getHeight() / 2;
377
+ const targetX = canvasCenterX - objectCenter.x * zoom;
378
+ const targetY = canvasCenterY - objectCenter.y * zoom;
379
+ if (!panOpts?.animate) {
380
+ const vt2 = canvas.viewportTransform;
381
+ if (!vt2) return;
382
+ canvas.setViewportTransform([
383
+ vt2[0],
384
+ vt2[1],
385
+ vt2[2],
386
+ vt2[3],
387
+ targetX,
388
+ targetY
389
+ ]);
390
+ return;
391
+ }
392
+ const duration = panOpts.duration ?? 300;
393
+ const vt = canvas.viewportTransform;
394
+ if (!vt) return;
395
+ const startX = vt[4];
396
+ const startY = vt[5];
397
+ const startTime = performance.now();
398
+ function step(now) {
399
+ const elapsed = now - startTime;
400
+ const t = Math.min(elapsed / duration, 1);
401
+ const eased = 1 - Math.pow(1 - t, 3);
402
+ const currentX = startX + (targetX - startX) * eased;
403
+ const currentY = startY + (targetY - startY) * eased;
404
+ const currentVt = canvas.viewportTransform;
405
+ if (!currentVt) return;
406
+ canvas.setViewportTransform([
407
+ currentVt[0],
408
+ currentVt[1],
409
+ currentVt[2],
410
+ currentVt[3],
411
+ currentX,
412
+ currentY
413
+ ]);
414
+ if (t < 1) {
415
+ requestAnimationFrame(step);
416
+ }
417
+ }
418
+ requestAnimationFrame(step);
419
+ },
328
420
  cleanup() {
329
421
  canvas.off("mouse:wheel", handleWheel);
330
422
  canvas.off("mouse:down", panHandlers.handleMouseDown);
@@ -339,10 +431,15 @@ function resetViewport(canvas) {
339
431
  }
340
432
 
341
433
  // src/background.ts
342
- var import_fabric3 = require("fabric");
434
+ var import_fabric4 = require("fabric");
343
435
  function getBackgroundImage(canvas) {
344
436
  return canvas.backgroundImage;
345
437
  }
438
+ function getBackgroundSrc(canvas) {
439
+ const bg = getBackgroundImage(canvas);
440
+ if (!bg) return null;
441
+ return bg.getSrc() || null;
442
+ }
346
443
  function fitViewportToBackground(canvas, options) {
347
444
  const bg = getBackgroundImage(canvas);
348
445
  if (!bg) return;
@@ -379,12 +476,12 @@ function setBackgroundInverted(canvas, inverted) {
379
476
  const bg = getBackgroundImage(canvas);
380
477
  if (!bg) return;
381
478
  const currentFilters = bg.filters ?? [];
382
- const hasInvert = currentFilters.some((f) => f instanceof import_fabric3.filters.Invert);
479
+ const hasInvert = currentFilters.some((f) => f instanceof import_fabric4.filters.Invert);
383
480
  if (inverted && !hasInvert) {
384
- bg.filters = [...currentFilters, new import_fabric3.filters.Invert()];
481
+ bg.filters = [...currentFilters, new import_fabric4.filters.Invert()];
385
482
  bg.applyFilters();
386
483
  } else if (!inverted && hasInvert) {
387
- bg.filters = currentFilters.filter((f) => !(f instanceof import_fabric3.filters.Invert));
484
+ bg.filters = currentFilters.filter((f) => !(f instanceof import_fabric4.filters.Invert));
388
485
  bg.applyFilters();
389
486
  }
390
487
  canvas.requestRenderAll();
@@ -392,7 +489,7 @@ function setBackgroundInverted(canvas, inverted) {
392
489
  function getBackgroundInverted(canvas) {
393
490
  const bg = getBackgroundImage(canvas);
394
491
  if (!bg?.filters) return false;
395
- return bg.filters.some((f) => f instanceof import_fabric3.filters.Invert);
492
+ return bg.filters.some((f) => f instanceof import_fabric4.filters.Invert);
396
493
  }
397
494
  function resizeImageUrl(url, options) {
398
495
  const maxSize = options?.maxSize ?? DEFAULT_IMAGE_MAX_SIZE;
@@ -438,14 +535,18 @@ function resizeImageUrl(url, options) {
438
535
  img.src = url;
439
536
  });
440
537
  }
441
- async function setBackgroundImage(canvas, url, resize) {
538
+ async function setBackgroundImage(canvas, url, options) {
539
+ const prevOpacity = options?.preserveOpacity ? getBackgroundOpacity(canvas) : void 0;
442
540
  let imageUrl = url;
443
- if (resize !== void 0) {
444
- const result = await resizeImageUrl(url, resize);
541
+ if (options !== void 0) {
542
+ const result = await resizeImageUrl(url, options);
445
543
  imageUrl = result.url;
446
544
  }
447
- const img = await import_fabric3.FabricImage.fromURL(imageUrl, { crossOrigin: "anonymous" });
545
+ const img = await import_fabric4.FabricImage.fromURL(imageUrl, { crossOrigin: "anonymous" });
448
546
  canvas.backgroundImage = img;
547
+ if (prevOpacity !== void 0 && prevOpacity !== 1) {
548
+ img.set("opacity", prevOpacity);
549
+ }
449
550
  canvas.requestRenderAll();
450
551
  return img;
451
552
  }
@@ -474,7 +575,10 @@ function createViewportActions(canvasRef, viewportRef, setZoom) {
474
575
  viewportRef.current?.zoomOut(step);
475
576
  syncZoom(canvasRef, setZoom);
476
577
  };
477
- return { resetViewport: resetViewport2, zoomIn, zoomOut };
578
+ const panToObject = (object, panOpts) => {
579
+ viewportRef.current?.panToObject(object, panOpts);
580
+ };
581
+ return { resetViewport: resetViewport2, zoomIn, zoomOut, panToObject };
478
582
  }
479
583
  function resolveAlignmentEnabled(enableAlignment, alignmentProp) {
480
584
  if (enableAlignment !== void 0) return enableAlignment;
@@ -482,22 +586,22 @@ function resolveAlignmentEnabled(enableAlignment, alignmentProp) {
482
586
  }
483
587
 
484
588
  // src/alignment/snapPoints.ts
485
- var import_fabric5 = require("fabric");
589
+ var import_fabric6 = require("fabric");
486
590
 
487
591
  // src/alignment/objectAlignmentUtils.ts
488
- var import_fabric4 = require("fabric");
592
+ var import_fabric5 = require("fabric");
489
593
  function getStrokeFreeCoords(obj) {
490
594
  const m = obj.calcTransformMatrix();
491
595
  const w = obj.width / 2;
492
596
  const h = obj.height / 2;
493
597
  return [
494
- new import_fabric4.Point(-w, -h).transform(m),
598
+ new import_fabric5.Point(-w, -h).transform(m),
495
599
  // tl
496
- new import_fabric4.Point(w, -h).transform(m),
600
+ new import_fabric5.Point(w, -h).transform(m),
497
601
  // tr
498
- new import_fabric4.Point(w, h).transform(m),
602
+ new import_fabric5.Point(w, h).transform(m),
499
603
  // br
500
- new import_fabric4.Point(-w, h).transform(m)
604
+ new import_fabric5.Point(-w, h).transform(m)
501
605
  // bl
502
606
  ];
503
607
  }
@@ -549,17 +653,17 @@ function getAlignmentTargets(target) {
549
653
  const objects = /* @__PURE__ */ new Set();
550
654
  const canvas = target.canvas;
551
655
  if (!canvas) return objects;
552
- const children = target instanceof import_fabric4.ActiveSelection ? target.getObjects() : [target];
656
+ const children = target instanceof import_fabric5.ActiveSelection ? target.getObjects() : [target];
553
657
  canvas.forEachObject((o) => {
554
658
  if (!o.isOnScreen() || !o.visible) return;
555
- if (o.constructor === import_fabric4.Group) {
659
+ if (o.constructor === import_fabric5.Group) {
556
660
  collectGroupChildren(objects, o);
557
661
  return;
558
662
  }
559
663
  objects.add(o);
560
664
  });
561
665
  for (const child of children) {
562
- if (child.constructor === import_fabric4.Group) {
666
+ if (child.constructor === import_fabric5.Group) {
563
667
  for (const gc of child.getObjects()) objects.delete(gc);
564
668
  } else {
565
669
  objects.delete(child);
@@ -570,7 +674,7 @@ function getAlignmentTargets(target) {
570
674
  function collectGroupChildren(objects, group) {
571
675
  for (const child of group.getObjects()) {
572
676
  if (!child.visible) continue;
573
- if (child.constructor === import_fabric4.Group) {
677
+ if (child.constructor === import_fabric5.Group) {
574
678
  collectGroupChildren(objects, child);
575
679
  } else {
576
680
  objects.add(child);
@@ -596,7 +700,7 @@ function getDefaultSnapPoints(object) {
596
700
  return [...coords, object.getCenterPoint()];
597
701
  }
598
702
  registerSnapPointExtractor(
599
- (obj) => obj instanceof import_fabric5.Rect,
703
+ (obj) => obj instanceof import_fabric6.Rect,
600
704
  (obj) => {
601
705
  const [tl, tr, br, bl] = getStrokeFreeCoords(obj);
602
706
  const mt = tl.add(tr).scalarDivide(2);
@@ -607,16 +711,16 @@ registerSnapPointExtractor(
607
711
  }
608
712
  );
609
713
  registerSnapPointExtractor(
610
- (obj) => obj instanceof import_fabric5.Polygon,
714
+ (obj) => obj instanceof import_fabric6.Polygon,
611
715
  (obj) => {
612
716
  const polygon = obj;
613
717
  const matrix = polygon.calcTransformMatrix();
614
718
  const points = polygon.points.map((pt) => {
615
- const local = new import_fabric5.Point(
719
+ const local = new import_fabric6.Point(
616
720
  pt.x - polygon.pathOffset.x,
617
721
  pt.y - polygon.pathOffset.y
618
722
  );
619
- return import_fabric5.util.transformPoint(local, matrix);
723
+ return import_fabric6.util.transformPoint(local, matrix);
620
724
  });
621
725
  points.push(polygon.getCenterPoint());
622
726
  return points;
@@ -624,7 +728,7 @@ registerSnapPointExtractor(
624
728
  );
625
729
 
626
730
  // src/alignment/objectAlignment.ts
627
- var import_fabric6 = require("fabric");
731
+ var import_fabric7 = require("fabric");
628
732
 
629
733
  // src/alignment/objectAlignmentRendering.ts
630
734
  function drawAlignmentLine(config, origin, target) {
@@ -927,7 +1031,7 @@ var ObjectAlignmentGuides = class {
927
1031
  const isCenter = e.transform.original.originX === "center" && e.transform.original.originY === "center";
928
1032
  if (isCenter) {
929
1033
  const p = target.group ? point.transform(
930
- import_fabric6.util.invertTransform(target.group.calcTransformMatrix())
1034
+ import_fabric7.util.invertTransform(target.group.calcTransformMatrix())
931
1035
  ) : point;
932
1036
  diagonalPoint = diagonalPoint.add(p).scalarDivide(2);
933
1037
  }
@@ -996,7 +1100,7 @@ function enableRotationSnap(canvas, options) {
996
1100
  }
997
1101
 
998
1102
  // src/alignment/cursorSnapping.ts
999
- var import_fabric7 = require("fabric");
1103
+ var import_fabric8 = require("fabric");
1000
1104
  function snapCursorPoint(canvas, rawPoint, options) {
1001
1105
  const zoom = canvas.getZoom();
1002
1106
  const scaleWithSize = options?.scaleWithCanvasSize !== false;
@@ -1039,7 +1143,7 @@ function snapCursorPoint(canvas, rawPoint, options) {
1039
1143
  const snapX = bestDx <= margin && snapTargetsX.length > 0;
1040
1144
  const snapY = bestDy <= margin && snapTargetsY.length > 0;
1041
1145
  return {
1042
- point: new import_fabric7.Point(
1146
+ point: new import_fabric8.Point(
1043
1147
  snapX ? snapTargetsX[0].x : rawPoint.x,
1044
1148
  snapY ? snapTargetsY[0].y : rawPoint.y
1045
1149
  ),
@@ -1071,7 +1175,7 @@ function drawCursorGuidelines(canvas, snapResult, style) {
1071
1175
  ctx.stroke();
1072
1176
  drawXMarker2(ctx, from, xSize);
1073
1177
  }
1074
- drawXMarker2(ctx, new import_fabric7.Point(to.x, to.y), xSize);
1178
+ drawXMarker2(ctx, new import_fabric8.Point(to.x, to.y), xSize);
1075
1179
  }
1076
1180
  if (snapResult.snapY && snapResult.alignTargetsY) {
1077
1181
  const to = snapResult.point;
@@ -1082,7 +1186,7 @@ function drawCursorGuidelines(canvas, snapResult, style) {
1082
1186
  ctx.stroke();
1083
1187
  drawXMarker2(ctx, from, xSize);
1084
1188
  }
1085
- drawXMarker2(ctx, new import_fabric7.Point(to.x, to.y), xSize);
1189
+ drawXMarker2(ctx, new import_fabric8.Point(to.x, to.y), xSize);
1086
1190
  }
1087
1191
  ctx.restore();
1088
1192
  }
@@ -1144,7 +1248,7 @@ function enableClickToCreate(canvas, factory, options) {
1144
1248
  }
1145
1249
 
1146
1250
  // src/interactions/dragToCreate.ts
1147
- var import_fabric9 = require("fabric");
1251
+ var import_fabric10 = require("fabric");
1148
1252
 
1149
1253
  // src/styles.ts
1150
1254
  var import_styles = require("@mui/material/styles");
@@ -1191,7 +1295,7 @@ var DEFAULT_ALIGNMENT_STYLE = {
1191
1295
  };
1192
1296
 
1193
1297
  // src/interactions/interactionSnapping.ts
1194
- var import_fabric8 = require("fabric");
1298
+ var import_fabric9 = require("fabric");
1195
1299
  var canvasAlignmentState = /* @__PURE__ */ new WeakMap();
1196
1300
  function setCanvasAlignmentEnabled(canvas, enabled) {
1197
1301
  canvasAlignmentState.set(canvas, enabled);
@@ -1239,7 +1343,7 @@ function createInteractionSnapping(canvas, options, getAdditionalTargets) {
1239
1343
  }
1240
1344
  function snap(rawX, rawY) {
1241
1345
  if (!snapEnabled) return { x: rawX, y: rawY };
1242
- const result = snapCursorPoint(canvas, new import_fabric8.Point(rawX, rawY), {
1346
+ const result = snapCursorPoint(canvas, new import_fabric9.Point(rawX, rawY), {
1243
1347
  margin: snapMargin,
1244
1348
  exclude: excludeSet,
1245
1349
  targetPoints: getAllTargetPoints()
@@ -1251,7 +1355,7 @@ function createInteractionSnapping(canvas, options, getAdditionalTargets) {
1251
1355
  lastSnapResult = null;
1252
1356
  return { x: rawX, y: rawY };
1253
1357
  }
1254
- lastSnapResult = snapCursorPoint(canvas, new import_fabric8.Point(rawX, rawY), {
1358
+ lastSnapResult = snapCursorPoint(canvas, new import_fabric9.Point(rawX, rawY), {
1255
1359
  margin: snapMargin,
1256
1360
  exclude: excludeSet,
1257
1361
  targetPoints: getAllTargetPoints()
@@ -1327,7 +1431,7 @@ function enableDragToCreate(canvas, factory, options) {
1327
1431
  lastEndY = startY;
1328
1432
  previousSelection = canvas.selection;
1329
1433
  canvas.selection = false;
1330
- previewRect = new import_fabric9.Rect({
1434
+ previewRect = new import_fabric10.Rect({
1331
1435
  ...DEFAULT_GUIDELINE_SHAPE_STYLE,
1332
1436
  left: startX,
1333
1437
  top: startY,
@@ -1366,37 +1470,55 @@ function enableDragToCreate(canvas, factory, options) {
1366
1470
  options?.onCreated?.(obj);
1367
1471
  previewRect = null;
1368
1472
  };
1473
+ const handleKeyDown = (e) => {
1474
+ if (e.key === "Escape") {
1475
+ e.stopImmediatePropagation();
1476
+ e.preventDefault();
1477
+ cleanup("cancel");
1478
+ }
1479
+ };
1369
1480
  canvas.on("mouse:down", handleMouseDown);
1370
1481
  canvas.on("mouse:move", handleMouseMove);
1371
1482
  canvas.on("mouse:up", handleMouseUp);
1372
- return () => {
1483
+ document.addEventListener("keydown", handleKeyDown, true);
1484
+ let exited = false;
1485
+ function cleanup(reason) {
1486
+ if (exited) return;
1487
+ exited = true;
1373
1488
  canvas.off("mouse:down", handleMouseDown);
1374
1489
  canvas.off("mouse:move", handleMouseMove);
1375
1490
  canvas.off("mouse:up", handleMouseUp);
1491
+ document.removeEventListener("keydown", handleKeyDown, true);
1376
1492
  shiftTracker.cleanup();
1377
1493
  snapping.cleanup();
1378
1494
  if (isDrawing && previewRect) {
1495
+ snapping.excludeSet.delete(previewRect);
1379
1496
  canvas.remove(previewRect);
1497
+ canvas.selection = previousSelection;
1380
1498
  canvas.requestRenderAll();
1381
1499
  }
1382
1500
  restoreViewport(options?.viewport);
1383
- };
1501
+ if (reason === "cancel") {
1502
+ options?.onCancel?.();
1503
+ }
1504
+ }
1505
+ return () => cleanup();
1384
1506
  }
1385
1507
 
1386
1508
  // src/interactions/drawToCreate.ts
1387
- var import_fabric13 = require("fabric");
1509
+ var import_fabric14 = require("fabric");
1388
1510
 
1389
1511
  // src/shapes/rectangle.ts
1390
- var import_fabric10 = require("fabric");
1512
+ var import_fabric11 = require("fabric");
1391
1513
  function createRectangle(canvas, options) {
1392
- const rect = new import_fabric10.Rect({ ...DEFAULT_SHAPE_STYLE, ...options });
1514
+ const rect = new import_fabric11.Rect({ ...DEFAULT_SHAPE_STYLE, ...options });
1393
1515
  canvas.add(rect);
1394
1516
  canvas.requestRenderAll();
1395
1517
  return rect;
1396
1518
  }
1397
1519
  function createRectangleAtPoint(canvas, point, options) {
1398
1520
  const { width, height, ...style } = options;
1399
- const rect = new import_fabric10.Rect({
1521
+ const rect = new import_fabric11.Rect({
1400
1522
  ...DEFAULT_SHAPE_STYLE,
1401
1523
  left: point.x,
1402
1524
  top: point.y,
@@ -1415,7 +1537,7 @@ function editRectangle(canvas, rect, changes) {
1415
1537
  }
1416
1538
 
1417
1539
  // src/shapes/circle.ts
1418
- var import_fabric11 = require("fabric");
1540
+ var import_fabric12 = require("fabric");
1419
1541
  var CIRCLE_CONSTRAINTS = {
1420
1542
  lockRotation: true,
1421
1543
  lockUniScaling: true
@@ -1437,7 +1559,7 @@ function restoreCircleConstraints(rect) {
1437
1559
  }
1438
1560
  function createCircle(canvas, options) {
1439
1561
  const { size, ...rest } = options;
1440
- const rect = new import_fabric11.Rect({
1562
+ const rect = new import_fabric12.Rect({
1441
1563
  ...DEFAULT_CIRCLE_STYLE,
1442
1564
  ...CIRCLE_CONSTRAINTS,
1443
1565
  width: size,
@@ -1453,7 +1575,7 @@ function createCircle(canvas, options) {
1453
1575
  }
1454
1576
  function createCircleAtPoint(canvas, point, options) {
1455
1577
  const { size, ...style } = options;
1456
- const rect = new import_fabric11.Rect({
1578
+ const rect = new import_fabric12.Rect({
1457
1579
  ...DEFAULT_CIRCLE_STYLE,
1458
1580
  ...CIRCLE_CONSTRAINTS,
1459
1581
  left: point.x,
@@ -1487,17 +1609,17 @@ function editCircle(canvas, rect, changes) {
1487
1609
  }
1488
1610
 
1489
1611
  // src/shapes/polygon.ts
1490
- var import_fabric12 = require("fabric");
1612
+ var import_fabric13 = require("fabric");
1491
1613
  function createPolygon(canvas, options) {
1492
1614
  const { points, ...rest } = options;
1493
- const polygon = new import_fabric12.Polygon(points, { ...DEFAULT_SHAPE_STYLE, ...rest });
1615
+ const polygon = new import_fabric13.Polygon(points, { ...DEFAULT_SHAPE_STYLE, ...rest });
1494
1616
  canvas.add(polygon);
1495
1617
  canvas.requestRenderAll();
1496
1618
  return polygon;
1497
1619
  }
1498
1620
  function createPolygonAtPoint(canvas, point, options) {
1499
1621
  const { width, height, ...style } = options;
1500
- const polygon = new import_fabric12.Polygon(
1622
+ const polygon = new import_fabric13.Polygon(
1501
1623
  [
1502
1624
  { x: 0, y: 0 },
1503
1625
  { x: width, y: 0 },
@@ -1515,7 +1637,7 @@ function createPolygonFromDrag(canvas, start, end, style) {
1515
1637
  const height = Math.abs(end.y - start.y);
1516
1638
  const left = Math.min(start.x, end.x) + width / 2;
1517
1639
  const top = Math.min(start.y, end.y) + height / 2;
1518
- const polygon = new import_fabric12.Polygon(
1640
+ const polygon = new import_fabric13.Polygon(
1519
1641
  [
1520
1642
  { x: 0, y: 0 },
1521
1643
  { x: width, y: 0 },
@@ -1529,7 +1651,7 @@ function createPolygonFromDrag(canvas, start, end, style) {
1529
1651
  return polygon;
1530
1652
  }
1531
1653
  function createPolygonFromVertices(canvas, points, style) {
1532
- const polygon = new import_fabric12.Polygon(
1654
+ const polygon = new import_fabric13.Polygon(
1533
1655
  points.map((p) => ({ x: p.x, y: p.y })),
1534
1656
  { ...DEFAULT_SHAPE_STYLE, ...style }
1535
1657
  );
@@ -1607,7 +1729,7 @@ function enableDrawToCreate(canvas, options) {
1607
1729
  const snapping = createInteractionSnapping(
1608
1730
  canvas,
1609
1731
  options,
1610
- () => points.map((p) => new import_fabric13.Point(p.x, p.y))
1732
+ () => points.map((p) => new import_fabric14.Point(p.x, p.y))
1611
1733
  );
1612
1734
  function trackPreviewElement(obj) {
1613
1735
  snapping.excludeSet.add(obj);
@@ -1656,6 +1778,9 @@ function enableDrawToCreate(canvas, options) {
1656
1778
  removePreviewElements();
1657
1779
  snapping.clearSnapResult();
1658
1780
  const polygon = createPolygonFromVertices(canvas, points, options?.style);
1781
+ if (options?.data) {
1782
+ polygon.data = options.data;
1783
+ }
1659
1784
  canvas.selection = previousSelection;
1660
1785
  canvas.requestRenderAll();
1661
1786
  restoreViewport(options?.viewport);
@@ -1689,7 +1814,7 @@ function enableDrawToCreate(canvas, options) {
1689
1814
  canvas.selection = false;
1690
1815
  }
1691
1816
  points.push({ x, y });
1692
- const marker = new import_fabric13.Circle({
1817
+ const marker = new import_fabric14.Circle({
1693
1818
  left: x,
1694
1819
  top: y,
1695
1820
  radius: 4,
@@ -1705,7 +1830,7 @@ function enableDrawToCreate(canvas, options) {
1705
1830
  canvas.add(marker);
1706
1831
  if (points.length >= 2) {
1707
1832
  const prev = points[points.length - 2];
1708
- const edge = new import_fabric13.Line([prev.x, prev.y, x, y], lineStyle);
1833
+ const edge = new import_fabric14.Line([prev.x, prev.y, x, y], lineStyle);
1709
1834
  edgeLines.push(edge);
1710
1835
  trackPreviewElement(edge);
1711
1836
  canvas.add(edge);
@@ -1734,7 +1859,7 @@ function enableDrawToCreate(canvas, options) {
1734
1859
  untrackPreviewElement(trackingLine);
1735
1860
  canvas.remove(trackingLine);
1736
1861
  }
1737
- trackingLine = new import_fabric13.Line([lastPoint.x, lastPoint.y, x, y], {
1862
+ trackingLine = new import_fabric14.Line([lastPoint.x, lastPoint.y, x, y], {
1738
1863
  ...guideLineStyle,
1739
1864
  strokeDashArray: [5, 5]
1740
1865
  });
@@ -1746,7 +1871,7 @@ function enableDrawToCreate(canvas, options) {
1746
1871
  closingLine = null;
1747
1872
  }
1748
1873
  if (points.length >= 3) {
1749
- closingLine = new import_fabric13.Line([x, y, points[0].x, points[0].y], {
1874
+ closingLine = new import_fabric14.Line([x, y, points[0].x, points[0].y], {
1750
1875
  ...guideLineStyle,
1751
1876
  strokeDashArray: [5, 5]
1752
1877
  });
@@ -1788,30 +1913,30 @@ function enableDrawToCreate(canvas, options) {
1788
1913
  }
1789
1914
 
1790
1915
  // src/interactions/vertexEdit.ts
1791
- var import_fabric14 = require("fabric");
1916
+ var import_fabric15 = require("fabric");
1792
1917
  function localPointToScene(polygon, point) {
1793
1918
  const matrix = polygon.calcTransformMatrix();
1794
- const localPoint = new import_fabric14.Point(
1919
+ const localPoint = new import_fabric15.Point(
1795
1920
  point.x - polygon.pathOffset.x,
1796
1921
  point.y - polygon.pathOffset.y
1797
1922
  );
1798
- return import_fabric14.util.transformPoint(localPoint, matrix);
1923
+ return import_fabric15.util.transformPoint(localPoint, matrix);
1799
1924
  }
1800
1925
  function scenePointToLocal(polygon, scenePoint) {
1801
1926
  const matrix = polygon.calcTransformMatrix();
1802
- const invMatrix = import_fabric14.util.invertTransform(matrix);
1803
- const localPoint = import_fabric14.util.transformPoint(scenePoint, invMatrix);
1927
+ const invMatrix = import_fabric15.util.invertTransform(matrix);
1928
+ const localPoint = import_fabric15.util.transformPoint(scenePoint, invMatrix);
1804
1929
  return {
1805
1930
  x: localPoint.x + polygon.pathOffset.x,
1806
1931
  y: localPoint.y + polygon.pathOffset.y
1807
1932
  };
1808
1933
  }
1809
1934
  function sceneToScreen(scenePoint, canvas) {
1810
- return import_fabric14.util.transformPoint(scenePoint, canvas.viewportTransform);
1935
+ return import_fabric15.util.transformPoint(scenePoint, canvas.viewportTransform);
1811
1936
  }
1812
1937
  function screenToScene(screenX, screenY, canvas) {
1813
- const inv = import_fabric14.util.invertTransform(canvas.viewportTransform);
1814
- return import_fabric14.util.transformPoint(new import_fabric14.Point(screenX, screenY), inv);
1938
+ const inv = import_fabric15.util.invertTransform(canvas.viewportTransform);
1939
+ return import_fabric15.util.transformPoint(new import_fabric15.Point(screenX, screenY), inv);
1815
1940
  }
1816
1941
  function createHandleElement(radius, fill, stroke, strokeWidth) {
1817
1942
  const el = document.createElement("div");
@@ -1901,7 +2026,7 @@ function enableVertexEdit(canvas, polygon, options, onExit) {
1901
2026
  const canvasRelY = e.clientY - wrapperRect.top;
1902
2027
  const rawScene = screenToScene(canvasRelX, canvasRelY, canvas);
1903
2028
  const snapped = snapping.snapWithGuidelines(rawScene.x, rawScene.y);
1904
- const scenePoint = new import_fabric14.Point(snapped.x, snapped.y);
2029
+ const scenePoint = new import_fabric15.Point(snapped.x, snapped.y);
1905
2030
  const anchorIdx = i === 0 ? 1 : 0;
1906
2031
  const anchorBefore = localPointToScene(
1907
2032
  polygon,
@@ -1974,7 +2099,7 @@ function enableVertexEdit(canvas, polygon, options, onExit) {
1974
2099
  }
1975
2100
 
1976
2101
  // src/serialization.ts
1977
- var import_fabric15 = require("fabric");
2102
+ var import_fabric16 = require("fabric");
1978
2103
  var strokeBaseMap = /* @__PURE__ */ new WeakMap();
1979
2104
  function enableScaledStrokes(canvas) {
1980
2105
  function applyScaledStrokes() {
@@ -2031,15 +2156,23 @@ function serializeCanvas(canvas, options) {
2031
2156
  });
2032
2157
  return json;
2033
2158
  }
2034
- async function loadCanvas(canvas, json) {
2159
+ async function loadCanvas(canvas, json, options) {
2035
2160
  await canvas.loadFromJSON(json);
2161
+ if (options?.filter) {
2162
+ const toRemove = [];
2163
+ canvas.forEachObject((obj) => {
2164
+ if (!options.filter(obj)) toRemove.push(obj);
2165
+ });
2166
+ for (const obj of toRemove) canvas.remove(obj);
2167
+ }
2036
2168
  canvas.forEachObject((obj) => {
2037
2169
  obj.set(DEFAULT_CONTROL_STYLE);
2038
- if (obj.shapeType === "circle" && obj instanceof import_fabric15.Rect) {
2170
+ if (obj.shapeType === "circle" && obj instanceof import_fabric16.Rect) {
2039
2171
  restoreCircleConstraints(obj);
2040
2172
  }
2041
2173
  });
2042
2174
  canvas.requestRenderAll();
2175
+ return canvas.getObjects();
2043
2176
  }
2044
2177
 
2045
2178
  // src/hooks/useEditCanvas.ts
@@ -2055,6 +2188,7 @@ function useEditCanvas(options) {
2055
2188
  const [selected, setSelected] = (0, import_react2.useState)([]);
2056
2189
  const [viewportMode, setViewportModeState] = (0, import_react2.useState)("select");
2057
2190
  const [isEditingVertices, setIsEditingVertices] = (0, import_react2.useState)(false);
2191
+ const [isDirty, setIsDirty] = (0, import_react2.useState)(false);
2058
2192
  const setMode = (0, import_react2.useCallback)((setup) => {
2059
2193
  vertexEditCleanupRef.current?.();
2060
2194
  vertexEditCleanupRef.current = null;
@@ -2125,10 +2259,15 @@ function useEditCanvas(options) {
2125
2259
  canvas.on("selection:cleared", () => {
2126
2260
  setSelected([]);
2127
2261
  });
2262
+ if (options?.trackChanges) {
2263
+ canvas.on("object:added", () => setIsDirty(true));
2264
+ canvas.on("object:removed", () => setIsDirty(true));
2265
+ canvas.on("object:modified", () => setIsDirty(true));
2266
+ }
2128
2267
  if (options?.vertexEdit !== false) {
2129
2268
  const vertexOpts = typeof options?.vertexEdit === "object" ? options.vertexEdit : void 0;
2130
2269
  canvas.on("mouse:dblclick", (e) => {
2131
- if (e.target && e.target instanceof import_fabric16.Polygon) {
2270
+ if (e.target && e.target instanceof import_fabric17.Polygon) {
2132
2271
  vertexEditCleanupRef.current?.();
2133
2272
  vertexEditCleanupRef.current = enableVertexEdit(
2134
2273
  canvas,
@@ -2179,22 +2318,26 @@ function useEditCanvas(options) {
2179
2318
  viewportRef.current?.setMode(mode);
2180
2319
  setViewportModeState(mode);
2181
2320
  }, []);
2182
- const { resetViewport: resetViewport2, zoomIn, zoomOut } = createViewportActions(
2321
+ const { resetViewport: resetViewport2, zoomIn, zoomOut, panToObject } = createViewportActions(
2183
2322
  canvasRef,
2184
2323
  viewportRef,
2185
2324
  setZoom
2186
2325
  );
2187
- const setBackground = (0, import_react2.useCallback)(async (url) => {
2188
- const canvas = canvasRef.current;
2189
- if (!canvas) throw new Error("Canvas not ready");
2190
- const resizeOpts = options?.backgroundResize !== false ? typeof options?.backgroundResize === "object" ? options.backgroundResize : {} : void 0;
2191
- const img = await setBackgroundImage(canvas, url, resizeOpts);
2192
- if (options?.autoFitToBackground !== false) {
2193
- fitViewportToBackground(canvas);
2194
- syncZoom(canvasRef, setZoom);
2195
- }
2196
- return img;
2197
- }, []);
2326
+ const setBackground = (0, import_react2.useCallback)(
2327
+ async (url, bgOpts) => {
2328
+ const canvas = canvasRef.current;
2329
+ if (!canvas) throw new Error("Canvas not ready");
2330
+ const resizeOpts = options?.backgroundResize !== false ? typeof options?.backgroundResize === "object" ? { ...options.backgroundResize, ...bgOpts } : { ...bgOpts } : bgOpts?.preserveOpacity ? { preserveOpacity: true } : void 0;
2331
+ const img = await setBackgroundImage(canvas, url, resizeOpts);
2332
+ if (options?.autoFitToBackground !== false) {
2333
+ fitViewportToBackground(canvas);
2334
+ syncZoom(canvasRef, setZoom);
2335
+ }
2336
+ return img;
2337
+ },
2338
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2339
+ []
2340
+ );
2198
2341
  return {
2199
2342
  /** Pass this to `<Canvas onReady={...} />` */
2200
2343
  onReady,
@@ -2215,7 +2358,9 @@ function useEditCanvas(options) {
2215
2358
  /** Zoom in toward the canvas center. Default step: 0.2. */
2216
2359
  zoomIn,
2217
2360
  /** Zoom out from the canvas center. Default step: 0.2. */
2218
- zoomOut
2361
+ zoomOut,
2362
+ /** Pan the viewport to center on a specific object. */
2363
+ panToObject
2219
2364
  },
2220
2365
  /** Whether vertex edit mode is currently active (reactive). */
2221
2366
  isEditingVertices,
@@ -2239,21 +2384,28 @@ function useEditCanvas(options) {
2239
2384
  * Set a background image from a URL. Automatically resizes if the image
2240
2385
  * exceeds the configured limits (opt out via `backgroundResize: false`),
2241
2386
  * and fits the viewport after setting if `autoFitToBackground` is enabled.
2387
+ *
2388
+ * Pass `{ preserveOpacity: true }` to keep the current background opacity
2389
+ * when replacing the image.
2242
2390
  */
2243
- setBackground
2391
+ setBackground,
2392
+ /** Whether the canvas has been modified since the last `resetDirty()` call. Requires `trackChanges: true`. */
2393
+ isDirty,
2394
+ /** Reset the dirty flag (e.g., after a successful save). */
2395
+ resetDirty: (0, import_react2.useCallback)(() => setIsDirty(false), [])
2244
2396
  };
2245
2397
  }
2246
2398
 
2247
2399
  // src/hooks/useViewCanvas.ts
2248
2400
  var import_react3 = require("react");
2249
- var import_fabric17 = require("fabric");
2401
+ var import_fabric18 = require("fabric");
2250
2402
  var VIEW_BORDER_RADIUS = 4;
2251
2403
  function lockCanvas(canvas) {
2252
2404
  canvas.selection = false;
2253
2405
  canvas.forEachObject((obj) => {
2254
2406
  obj.selectable = false;
2255
2407
  obj.evented = false;
2256
- if (obj instanceof import_fabric17.Rect && obj.shapeType !== "circle" && obj.data?.type !== "DEVICE") {
2408
+ if (obj instanceof import_fabric18.Rect && obj.shapeType !== "circle" && obj.data?.type !== "DEVICE") {
2257
2409
  const rx = VIEW_BORDER_RADIUS / (obj.scaleX ?? 1);
2258
2410
  const ry = VIEW_BORDER_RADIUS / (obj.scaleY ?? 1);
2259
2411
  obj.set({ rx, ry });
@@ -2298,7 +2450,7 @@ function useViewCanvas(options) {
2298
2450
  // eslint-disable-next-line react-hooks/exhaustive-deps
2299
2451
  []
2300
2452
  );
2301
- const { resetViewport: resetViewport2, zoomIn, zoomOut } = createViewportActions(
2453
+ const { resetViewport: resetViewport2, zoomIn, zoomOut, panToObject } = createViewportActions(
2302
2454
  canvasRef,
2303
2455
  viewportRef,
2304
2456
  setZoom
@@ -2364,7 +2516,9 @@ function useViewCanvas(options) {
2364
2516
  /** Zoom in toward the canvas center. Default step: 0.2. */
2365
2517
  zoomIn,
2366
2518
  /** Zoom out from the canvas center. Default step: 0.2. */
2367
- zoomOut
2519
+ zoomOut,
2520
+ /** Pan the viewport to center on a specific object. */
2521
+ panToObject
2368
2522
  },
2369
2523
  /** Update a single object's visual style by its `data.id`. */
2370
2524
  setObjectStyle,
@@ -2374,6 +2528,209 @@ function useViewCanvas(options) {
2374
2528
  setObjectStyleByType
2375
2529
  };
2376
2530
  }
2531
+
2532
+ // src/hooks/useCanvasEvents.ts
2533
+ var import_react4 = require("react");
2534
+ function useCanvasEvents(canvasRef, events) {
2535
+ const eventsRef = (0, import_react4.useRef)(events);
2536
+ eventsRef.current = events;
2537
+ (0, import_react4.useEffect)(() => {
2538
+ const canvas = canvasRef.current;
2539
+ if (!canvas) return;
2540
+ const wrappers = /* @__PURE__ */ new Map();
2541
+ for (const key of Object.keys(eventsRef.current)) {
2542
+ if (!eventsRef.current[key]) continue;
2543
+ const wrapped = (options) => {
2544
+ eventsRef.current[key]?.(options);
2545
+ };
2546
+ canvas.on(key, wrapped);
2547
+ wrappers.set(key, wrapped);
2548
+ }
2549
+ return () => {
2550
+ wrappers.forEach((handler, name) => {
2551
+ canvas.off(name, handler);
2552
+ });
2553
+ };
2554
+ }, [canvasRef]);
2555
+ }
2556
+
2557
+ // src/hooks/useCanvasTooltip.ts
2558
+ var import_react5 = require("react");
2559
+ function useCanvasTooltip(canvasRef, options) {
2560
+ const [state, setState] = (0, import_react5.useState)({
2561
+ visible: false,
2562
+ content: null,
2563
+ position: { x: 0, y: 0 }
2564
+ });
2565
+ const hoveredObjectRef = (0, import_react5.useRef)(null);
2566
+ const optionsRef = (0, import_react5.useRef)(options);
2567
+ optionsRef.current = options;
2568
+ (0, import_react5.useEffect)(() => {
2569
+ const canvas = canvasRef.current;
2570
+ if (!canvas) return;
2571
+ function calculatePosition(target) {
2572
+ const bounds = target.getBoundingRect();
2573
+ const zoom = canvas.getZoom();
2574
+ const vt = canvas.viewportTransform;
2575
+ if (!vt) return null;
2576
+ return {
2577
+ x: (bounds.left + bounds.width / 2) * zoom + vt[4],
2578
+ y: bounds.top * zoom + vt[5] - 10
2579
+ };
2580
+ }
2581
+ const handleMouseOver = (e) => {
2582
+ const target = e.target;
2583
+ if (!target) return;
2584
+ const content = optionsRef.current.getContent(target);
2585
+ if (content === null) return;
2586
+ if (hoveredObjectRef.current !== target) {
2587
+ hoveredObjectRef.current = target;
2588
+ const position = calculatePosition(target);
2589
+ if (position) {
2590
+ setState({ visible: true, content, position });
2591
+ }
2592
+ }
2593
+ };
2594
+ const handleMouseOut = (e) => {
2595
+ if (e.target && hoveredObjectRef.current === e.target) {
2596
+ setState((prev) => ({ ...prev, visible: false }));
2597
+ hoveredObjectRef.current = null;
2598
+ }
2599
+ };
2600
+ const updatePosition = () => {
2601
+ if (!hoveredObjectRef.current) return;
2602
+ const position = calculatePosition(hoveredObjectRef.current);
2603
+ if (position) {
2604
+ setState((prev) => prev.visible ? { ...prev, position } : prev);
2605
+ }
2606
+ };
2607
+ canvas.on("mouse:over", handleMouseOver);
2608
+ canvas.on("mouse:out", handleMouseOut);
2609
+ canvas.on("after:render", updatePosition);
2610
+ canvas.on("mouse:wheel", updatePosition);
2611
+ return () => {
2612
+ canvas.off("mouse:over", handleMouseOver);
2613
+ canvas.off("mouse:out", handleMouseOut);
2614
+ canvas.off("after:render", updatePosition);
2615
+ canvas.off("mouse:wheel", updatePosition);
2616
+ };
2617
+ }, [canvasRef]);
2618
+ return state;
2619
+ }
2620
+
2621
+ // src/hooks/useCanvasClick.ts
2622
+ var import_react6 = require("react");
2623
+ function useCanvasClick(canvasRef, onClick, options) {
2624
+ const onClickRef = (0, import_react6.useRef)(onClick);
2625
+ onClickRef.current = onClick;
2626
+ const optionsRef = (0, import_react6.useRef)(options);
2627
+ optionsRef.current = options;
2628
+ (0, import_react6.useEffect)(() => {
2629
+ const canvas = canvasRef.current;
2630
+ if (!canvas) return;
2631
+ let mouseDown = null;
2632
+ let isPanning = false;
2633
+ const handleMouseDown = (e) => {
2634
+ const native = e.e instanceof TouchEvent ? e.e.touches[0] : e.e;
2635
+ if (native) {
2636
+ mouseDown = { x: native.clientX, y: native.clientY, time: Date.now() };
2637
+ isPanning = false;
2638
+ }
2639
+ };
2640
+ const handleMouseMove = (e) => {
2641
+ if (!mouseDown) return;
2642
+ const native = e.e instanceof TouchEvent ? e.e.touches[0] : e.e;
2643
+ if (!native) return;
2644
+ const threshold = optionsRef.current?.threshold ?? 5;
2645
+ const deltaX = Math.abs(native.clientX - mouseDown.x);
2646
+ const deltaY = Math.abs(native.clientY - mouseDown.y);
2647
+ if (deltaX > threshold || deltaY > threshold) {
2648
+ isPanning = true;
2649
+ }
2650
+ };
2651
+ const handleMouseUp = (e) => {
2652
+ if (!mouseDown) return;
2653
+ const maxDuration = optionsRef.current?.maxDuration ?? 300;
2654
+ const elapsed = Date.now() - mouseDown.time;
2655
+ if (!isPanning && elapsed < maxDuration) {
2656
+ onClickRef.current(e.target);
2657
+ }
2658
+ mouseDown = null;
2659
+ isPanning = false;
2660
+ };
2661
+ canvas.on("mouse:down", handleMouseDown);
2662
+ canvas.on("mouse:move", handleMouseMove);
2663
+ canvas.on("mouse:up", handleMouseUp);
2664
+ return () => {
2665
+ canvas.off("mouse:down", handleMouseDown);
2666
+ canvas.off("mouse:move", handleMouseMove);
2667
+ canvas.off("mouse:up", handleMouseUp);
2668
+ };
2669
+ }, [canvasRef]);
2670
+ }
2671
+
2672
+ // src/hooks/useObjectOverlay.ts
2673
+ var import_react7 = require("react");
2674
+ var import_fabric19 = require("fabric");
2675
+ function useObjectOverlay(canvasRef, object, options) {
2676
+ const containerRef = (0, import_react7.useRef)(null);
2677
+ const optionsRef = (0, import_react7.useRef)(options);
2678
+ optionsRef.current = options;
2679
+ (0, import_react7.useEffect)(() => {
2680
+ const canvas = canvasRef.current;
2681
+ if (!canvas || !object) return;
2682
+ function update() {
2683
+ const el = containerRef.current;
2684
+ if (!el || !canvas || !object) return;
2685
+ const zoom = canvas.getZoom();
2686
+ const vt = canvas.viewportTransform;
2687
+ if (!vt) return;
2688
+ const center = object.getCenterPoint();
2689
+ const actualWidth = (object.width ?? 0) * (object.scaleX ?? 1);
2690
+ const actualHeight = (object.height ?? 0) * (object.scaleY ?? 1);
2691
+ const screenCoords = import_fabric19.util.transformPoint(center, vt);
2692
+ const screenWidth = actualWidth * zoom;
2693
+ const screenHeight = actualHeight * zoom;
2694
+ el.style.left = `${screenCoords.x - screenWidth / 2}px`;
2695
+ el.style.top = `${screenCoords.y - screenHeight / 2}px`;
2696
+ el.style.width = `${screenWidth}px`;
2697
+ el.style.height = `${screenHeight}px`;
2698
+ const angle = object.angle ?? 0;
2699
+ el.style.rotate = angle !== 0 ? `${angle}deg` : "";
2700
+ const opts = optionsRef.current;
2701
+ if (opts?.autoScaleContent) {
2702
+ const contentScale = Math.min(screenWidth, screenHeight) / 100;
2703
+ const clampedScale = Math.max(0.1, Math.min(contentScale, 2));
2704
+ el.style.setProperty("--overlay-scale", String(clampedScale));
2705
+ if (opts.textSelector) {
2706
+ const textMinScale = opts.textMinScale ?? 0.5;
2707
+ const textEls = el.querySelectorAll(opts.textSelector);
2708
+ const display = clampedScale < textMinScale ? "none" : "";
2709
+ textEls.forEach((t) => {
2710
+ t.style.display = display;
2711
+ });
2712
+ }
2713
+ }
2714
+ }
2715
+ update();
2716
+ canvas.on("after:render", update);
2717
+ canvas.on("mouse:wheel", update);
2718
+ object.on("moving", update);
2719
+ object.on("scaling", update);
2720
+ object.on("rotating", update);
2721
+ return () => {
2722
+ canvas.off("after:render", update);
2723
+ canvas.off("mouse:wheel", update);
2724
+ object.off("moving", update);
2725
+ object.off("scaling", update);
2726
+ object.off("rotating", update);
2727
+ };
2728
+ }, [canvasRef, object]);
2729
+ return containerRef;
2730
+ }
2731
+
2732
+ // src/index.ts
2733
+ var import_fabric20 = require("fabric");
2377
2734
  // Annotate the CommonJS export names for ESM import in node:
2378
2735
  0 && (module.exports = {
2379
2736
  Canvas,
@@ -2383,6 +2740,12 @@ function useViewCanvas(options) {
2383
2740
  DEFAULT_DRAG_SHAPE_STYLE,
2384
2741
  DEFAULT_GUIDELINE_SHAPE_STYLE,
2385
2742
  DEFAULT_SHAPE_STYLE,
2743
+ FabricCanvas,
2744
+ FabricImage,
2745
+ FabricObject,
2746
+ Point,
2747
+ Polygon,
2748
+ Rect,
2386
2749
  createCircle,
2387
2750
  createCircleAtPoint,
2388
2751
  createPolygon,
@@ -2407,6 +2770,7 @@ function useViewCanvas(options) {
2407
2770
  fitViewportToBackground,
2408
2771
  getBackgroundInverted,
2409
2772
  getBackgroundOpacity,
2773
+ getBackgroundSrc,
2410
2774
  getBaseStrokeWidth,
2411
2775
  getSnapPoints,
2412
2776
  loadCanvas,
@@ -2418,7 +2782,12 @@ function useViewCanvas(options) {
2418
2782
  setBackgroundInverted,
2419
2783
  setBackgroundOpacity,
2420
2784
  snapCursorPoint,
2785
+ useCanvasClick,
2786
+ useCanvasEvents,
2787
+ useCanvasTooltip,
2421
2788
  useEditCanvas,
2422
- useViewCanvas
2789
+ useObjectOverlay,
2790
+ useViewCanvas,
2791
+ util
2423
2792
  });
2424
2793
  //# sourceMappingURL=index.cjs.map