@bwp-web/canvas 0.4.0 → 0.4.2

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.
package/dist/index.cjs CHANGED
@@ -27,6 +27,11 @@ __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_fabric19.Canvas,
31
+ FabricImage: () => import_fabric19.FabricImage,
32
+ FabricObject: () => import_fabric19.FabricObject,
33
+ Polygon: () => import_fabric19.Polygon,
34
+ Rect: () => import_fabric19.Rect,
30
35
  createCircle: () => createCircle,
31
36
  createCircleAtPoint: () => createCircleAtPoint,
32
37
  createPolygon: () => createPolygon,
@@ -62,13 +67,19 @@ __export(index_exports, {
62
67
  setBackgroundInverted: () => setBackgroundInverted,
63
68
  setBackgroundOpacity: () => setBackgroundOpacity,
64
69
  snapCursorPoint: () => snapCursorPoint,
70
+ useCanvasClick: () => useCanvasClick,
71
+ useCanvasEvents: () => useCanvasEvents,
72
+ useCanvasTooltip: () => useCanvasTooltip,
65
73
  useEditCanvas: () => useEditCanvas,
66
74
  useViewCanvas: () => useViewCanvas
67
75
  });
68
76
  module.exports = __toCommonJS(index_exports);
69
77
 
70
- // src/Canvas/Canvas.tsx
78
+ // src/fabricAugmentation.ts
71
79
  var import_fabric = require("fabric");
80
+
81
+ // src/Canvas/Canvas.tsx
82
+ var import_fabric2 = require("fabric");
72
83
  var import_react = require("react");
73
84
 
74
85
  // src/keyboard.ts
@@ -107,7 +118,7 @@ function Canvas({
107
118
  if (!el) {
108
119
  return;
109
120
  }
110
- const fabricCanvas = new import_fabric.Canvas(el, { width, height });
121
+ const fabricCanvas = new import_fabric2.Canvas(el, { width, height });
111
122
  onReady?.(fabricCanvas);
112
123
  const cleanupShortcuts = enableKeyboardShortcuts(fabricCanvas);
113
124
  return () => {
@@ -120,10 +131,10 @@ function Canvas({
120
131
 
121
132
  // src/hooks/useEditCanvas.ts
122
133
  var import_react2 = require("react");
123
- var import_fabric16 = require("fabric");
134
+ var import_fabric17 = require("fabric");
124
135
 
125
136
  // src/viewport.ts
126
- var import_fabric2 = require("fabric");
137
+ var import_fabric3 = require("fabric");
127
138
 
128
139
  // src/constants.ts
129
140
  var DEFAULT_MIN_ZOOM = 0.2;
@@ -158,7 +169,7 @@ function touchDistance(touches) {
158
169
  }
159
170
  function touchMidpoint(touches, el) {
160
171
  const rect = el.getBoundingClientRect();
161
- return new import_fabric2.Point(
172
+ return new import_fabric3.Point(
162
173
  (touches[0].clientX + touches[1].clientX) / 2 - rect.left,
163
174
  (touches[0].clientY + touches[1].clientY) / 2 - rect.top
164
175
  );
@@ -173,7 +184,7 @@ function setupWheelZoom(canvas, bounds, zoomFactor, isEnabled) {
173
184
  let zoom = canvas.getZoom();
174
185
  zoom = delta < 0 ? zoom * zoomFactor : zoom / zoomFactor;
175
186
  zoom = Math.min(Math.max(zoom, bounds.minZoom), bounds.maxZoom);
176
- canvas.zoomToPoint(new import_fabric2.Point(e.offsetX, e.offsetY), zoom);
187
+ canvas.zoomToPoint(new import_fabric3.Point(e.offsetX, e.offsetY), zoom);
177
188
  };
178
189
  canvas.on("mouse:wheel", handleWheel);
179
190
  return handleWheel;
@@ -211,7 +222,7 @@ function setupMousePan(canvas, getMode, isEnabled) {
211
222
  const dy = pos.y - lastPanY;
212
223
  lastPanX = pos.x;
213
224
  lastPanY = pos.y;
214
- canvas.relativePan(new import_fabric2.Point(dx, dy));
225
+ canvas.relativePan(new import_fabric3.Point(dx, dy));
215
226
  canvas.setCursor("grab");
216
227
  };
217
228
  const handleMouseUp = () => {
@@ -314,14 +325,14 @@ function enablePanAndZoom(canvas, options) {
314
325
  zoomIn(step = DEFAULT_ZOOM_STEP) {
315
326
  const z = Math.min(canvas.getZoom() + step, bounds.maxZoom);
316
327
  canvas.zoomToPoint(
317
- new import_fabric2.Point(canvas.getWidth() / 2, canvas.getHeight() / 2),
328
+ new import_fabric3.Point(canvas.getWidth() / 2, canvas.getHeight() / 2),
318
329
  z
319
330
  );
320
331
  },
321
332
  zoomOut(step = DEFAULT_ZOOM_STEP) {
322
333
  const z = Math.max(canvas.getZoom() - step, bounds.minZoom);
323
334
  canvas.zoomToPoint(
324
- new import_fabric2.Point(canvas.getWidth() / 2, canvas.getHeight() / 2),
335
+ new import_fabric3.Point(canvas.getWidth() / 2, canvas.getHeight() / 2),
325
336
  z
326
337
  );
327
338
  },
@@ -339,7 +350,7 @@ function resetViewport(canvas) {
339
350
  }
340
351
 
341
352
  // src/background.ts
342
- var import_fabric3 = require("fabric");
353
+ var import_fabric4 = require("fabric");
343
354
  function getBackgroundImage(canvas) {
344
355
  return canvas.backgroundImage;
345
356
  }
@@ -379,12 +390,12 @@ function setBackgroundInverted(canvas, inverted) {
379
390
  const bg = getBackgroundImage(canvas);
380
391
  if (!bg) return;
381
392
  const currentFilters = bg.filters ?? [];
382
- const hasInvert = currentFilters.some((f) => f instanceof import_fabric3.filters.Invert);
393
+ const hasInvert = currentFilters.some((f) => f instanceof import_fabric4.filters.Invert);
383
394
  if (inverted && !hasInvert) {
384
- bg.filters = [...currentFilters, new import_fabric3.filters.Invert()];
395
+ bg.filters = [...currentFilters, new import_fabric4.filters.Invert()];
385
396
  bg.applyFilters();
386
397
  } else if (!inverted && hasInvert) {
387
- bg.filters = currentFilters.filter((f) => !(f instanceof import_fabric3.filters.Invert));
398
+ bg.filters = currentFilters.filter((f) => !(f instanceof import_fabric4.filters.Invert));
388
399
  bg.applyFilters();
389
400
  }
390
401
  canvas.requestRenderAll();
@@ -392,7 +403,7 @@ function setBackgroundInverted(canvas, inverted) {
392
403
  function getBackgroundInverted(canvas) {
393
404
  const bg = getBackgroundImage(canvas);
394
405
  if (!bg?.filters) return false;
395
- return bg.filters.some((f) => f instanceof import_fabric3.filters.Invert);
406
+ return bg.filters.some((f) => f instanceof import_fabric4.filters.Invert);
396
407
  }
397
408
  function resizeImageUrl(url, options) {
398
409
  const maxSize = options?.maxSize ?? DEFAULT_IMAGE_MAX_SIZE;
@@ -438,14 +449,18 @@ function resizeImageUrl(url, options) {
438
449
  img.src = url;
439
450
  });
440
451
  }
441
- async function setBackgroundImage(canvas, url, resize) {
452
+ async function setBackgroundImage(canvas, url, options) {
453
+ const prevOpacity = options?.preserveOpacity ? getBackgroundOpacity(canvas) : void 0;
442
454
  let imageUrl = url;
443
- if (resize !== void 0) {
444
- const result = await resizeImageUrl(url, resize);
455
+ if (options !== void 0) {
456
+ const result = await resizeImageUrl(url, options);
445
457
  imageUrl = result.url;
446
458
  }
447
- const img = await import_fabric3.FabricImage.fromURL(imageUrl, { crossOrigin: "anonymous" });
459
+ const img = await import_fabric4.FabricImage.fromURL(imageUrl, { crossOrigin: "anonymous" });
448
460
  canvas.backgroundImage = img;
461
+ if (prevOpacity !== void 0 && prevOpacity !== 1) {
462
+ img.set("opacity", prevOpacity);
463
+ }
449
464
  canvas.requestRenderAll();
450
465
  return img;
451
466
  }
@@ -482,22 +497,22 @@ function resolveAlignmentEnabled(enableAlignment, alignmentProp) {
482
497
  }
483
498
 
484
499
  // src/alignment/snapPoints.ts
485
- var import_fabric5 = require("fabric");
500
+ var import_fabric6 = require("fabric");
486
501
 
487
502
  // src/alignment/objectAlignmentUtils.ts
488
- var import_fabric4 = require("fabric");
503
+ var import_fabric5 = require("fabric");
489
504
  function getStrokeFreeCoords(obj) {
490
505
  const m = obj.calcTransformMatrix();
491
506
  const w = obj.width / 2;
492
507
  const h = obj.height / 2;
493
508
  return [
494
- new import_fabric4.Point(-w, -h).transform(m),
509
+ new import_fabric5.Point(-w, -h).transform(m),
495
510
  // tl
496
- new import_fabric4.Point(w, -h).transform(m),
511
+ new import_fabric5.Point(w, -h).transform(m),
497
512
  // tr
498
- new import_fabric4.Point(w, h).transform(m),
513
+ new import_fabric5.Point(w, h).transform(m),
499
514
  // br
500
- new import_fabric4.Point(-w, h).transform(m)
515
+ new import_fabric5.Point(-w, h).transform(m)
501
516
  // bl
502
517
  ];
503
518
  }
@@ -549,17 +564,17 @@ function getAlignmentTargets(target) {
549
564
  const objects = /* @__PURE__ */ new Set();
550
565
  const canvas = target.canvas;
551
566
  if (!canvas) return objects;
552
- const children = target instanceof import_fabric4.ActiveSelection ? target.getObjects() : [target];
567
+ const children = target instanceof import_fabric5.ActiveSelection ? target.getObjects() : [target];
553
568
  canvas.forEachObject((o) => {
554
569
  if (!o.isOnScreen() || !o.visible) return;
555
- if (o.constructor === import_fabric4.Group) {
570
+ if (o.constructor === import_fabric5.Group) {
556
571
  collectGroupChildren(objects, o);
557
572
  return;
558
573
  }
559
574
  objects.add(o);
560
575
  });
561
576
  for (const child of children) {
562
- if (child.constructor === import_fabric4.Group) {
577
+ if (child.constructor === import_fabric5.Group) {
563
578
  for (const gc of child.getObjects()) objects.delete(gc);
564
579
  } else {
565
580
  objects.delete(child);
@@ -570,7 +585,7 @@ function getAlignmentTargets(target) {
570
585
  function collectGroupChildren(objects, group) {
571
586
  for (const child of group.getObjects()) {
572
587
  if (!child.visible) continue;
573
- if (child.constructor === import_fabric4.Group) {
588
+ if (child.constructor === import_fabric5.Group) {
574
589
  collectGroupChildren(objects, child);
575
590
  } else {
576
591
  objects.add(child);
@@ -596,7 +611,7 @@ function getDefaultSnapPoints(object) {
596
611
  return [...coords, object.getCenterPoint()];
597
612
  }
598
613
  registerSnapPointExtractor(
599
- (obj) => obj instanceof import_fabric5.Rect,
614
+ (obj) => obj instanceof import_fabric6.Rect,
600
615
  (obj) => {
601
616
  const [tl, tr, br, bl] = getStrokeFreeCoords(obj);
602
617
  const mt = tl.add(tr).scalarDivide(2);
@@ -607,16 +622,16 @@ registerSnapPointExtractor(
607
622
  }
608
623
  );
609
624
  registerSnapPointExtractor(
610
- (obj) => obj instanceof import_fabric5.Polygon,
625
+ (obj) => obj instanceof import_fabric6.Polygon,
611
626
  (obj) => {
612
627
  const polygon = obj;
613
628
  const matrix = polygon.calcTransformMatrix();
614
629
  const points = polygon.points.map((pt) => {
615
- const local = new import_fabric5.Point(
630
+ const local = new import_fabric6.Point(
616
631
  pt.x - polygon.pathOffset.x,
617
632
  pt.y - polygon.pathOffset.y
618
633
  );
619
- return import_fabric5.util.transformPoint(local, matrix);
634
+ return import_fabric6.util.transformPoint(local, matrix);
620
635
  });
621
636
  points.push(polygon.getCenterPoint());
622
637
  return points;
@@ -624,7 +639,7 @@ registerSnapPointExtractor(
624
639
  );
625
640
 
626
641
  // src/alignment/objectAlignment.ts
627
- var import_fabric6 = require("fabric");
642
+ var import_fabric7 = require("fabric");
628
643
 
629
644
  // src/alignment/objectAlignmentRendering.ts
630
645
  function drawAlignmentLine(config, origin, target) {
@@ -927,7 +942,7 @@ var ObjectAlignmentGuides = class {
927
942
  const isCenter = e.transform.original.originX === "center" && e.transform.original.originY === "center";
928
943
  if (isCenter) {
929
944
  const p = target.group ? point.transform(
930
- import_fabric6.util.invertTransform(target.group.calcTransformMatrix())
945
+ import_fabric7.util.invertTransform(target.group.calcTransformMatrix())
931
946
  ) : point;
932
947
  diagonalPoint = diagonalPoint.add(p).scalarDivide(2);
933
948
  }
@@ -996,7 +1011,7 @@ function enableRotationSnap(canvas, options) {
996
1011
  }
997
1012
 
998
1013
  // src/alignment/cursorSnapping.ts
999
- var import_fabric7 = require("fabric");
1014
+ var import_fabric8 = require("fabric");
1000
1015
  function snapCursorPoint(canvas, rawPoint, options) {
1001
1016
  const zoom = canvas.getZoom();
1002
1017
  const scaleWithSize = options?.scaleWithCanvasSize !== false;
@@ -1039,7 +1054,7 @@ function snapCursorPoint(canvas, rawPoint, options) {
1039
1054
  const snapX = bestDx <= margin && snapTargetsX.length > 0;
1040
1055
  const snapY = bestDy <= margin && snapTargetsY.length > 0;
1041
1056
  return {
1042
- point: new import_fabric7.Point(
1057
+ point: new import_fabric8.Point(
1043
1058
  snapX ? snapTargetsX[0].x : rawPoint.x,
1044
1059
  snapY ? snapTargetsY[0].y : rawPoint.y
1045
1060
  ),
@@ -1071,7 +1086,7 @@ function drawCursorGuidelines(canvas, snapResult, style) {
1071
1086
  ctx.stroke();
1072
1087
  drawXMarker2(ctx, from, xSize);
1073
1088
  }
1074
- drawXMarker2(ctx, new import_fabric7.Point(to.x, to.y), xSize);
1089
+ drawXMarker2(ctx, new import_fabric8.Point(to.x, to.y), xSize);
1075
1090
  }
1076
1091
  if (snapResult.snapY && snapResult.alignTargetsY) {
1077
1092
  const to = snapResult.point;
@@ -1082,7 +1097,7 @@ function drawCursorGuidelines(canvas, snapResult, style) {
1082
1097
  ctx.stroke();
1083
1098
  drawXMarker2(ctx, from, xSize);
1084
1099
  }
1085
- drawXMarker2(ctx, new import_fabric7.Point(to.x, to.y), xSize);
1100
+ drawXMarker2(ctx, new import_fabric8.Point(to.x, to.y), xSize);
1086
1101
  }
1087
1102
  ctx.restore();
1088
1103
  }
@@ -1144,7 +1159,7 @@ function enableClickToCreate(canvas, factory, options) {
1144
1159
  }
1145
1160
 
1146
1161
  // src/interactions/dragToCreate.ts
1147
- var import_fabric9 = require("fabric");
1162
+ var import_fabric10 = require("fabric");
1148
1163
 
1149
1164
  // src/styles.ts
1150
1165
  var import_styles = require("@mui/material/styles");
@@ -1191,7 +1206,7 @@ var DEFAULT_ALIGNMENT_STYLE = {
1191
1206
  };
1192
1207
 
1193
1208
  // src/interactions/interactionSnapping.ts
1194
- var import_fabric8 = require("fabric");
1209
+ var import_fabric9 = require("fabric");
1195
1210
  var canvasAlignmentState = /* @__PURE__ */ new WeakMap();
1196
1211
  function setCanvasAlignmentEnabled(canvas, enabled) {
1197
1212
  canvasAlignmentState.set(canvas, enabled);
@@ -1239,7 +1254,7 @@ function createInteractionSnapping(canvas, options, getAdditionalTargets) {
1239
1254
  }
1240
1255
  function snap(rawX, rawY) {
1241
1256
  if (!snapEnabled) return { x: rawX, y: rawY };
1242
- const result = snapCursorPoint(canvas, new import_fabric8.Point(rawX, rawY), {
1257
+ const result = snapCursorPoint(canvas, new import_fabric9.Point(rawX, rawY), {
1243
1258
  margin: snapMargin,
1244
1259
  exclude: excludeSet,
1245
1260
  targetPoints: getAllTargetPoints()
@@ -1251,7 +1266,7 @@ function createInteractionSnapping(canvas, options, getAdditionalTargets) {
1251
1266
  lastSnapResult = null;
1252
1267
  return { x: rawX, y: rawY };
1253
1268
  }
1254
- lastSnapResult = snapCursorPoint(canvas, new import_fabric8.Point(rawX, rawY), {
1269
+ lastSnapResult = snapCursorPoint(canvas, new import_fabric9.Point(rawX, rawY), {
1255
1270
  margin: snapMargin,
1256
1271
  exclude: excludeSet,
1257
1272
  targetPoints: getAllTargetPoints()
@@ -1327,7 +1342,7 @@ function enableDragToCreate(canvas, factory, options) {
1327
1342
  lastEndY = startY;
1328
1343
  previousSelection = canvas.selection;
1329
1344
  canvas.selection = false;
1330
- previewRect = new import_fabric9.Rect({
1345
+ previewRect = new import_fabric10.Rect({
1331
1346
  ...DEFAULT_GUIDELINE_SHAPE_STYLE,
1332
1347
  left: startX,
1333
1348
  top: startY,
@@ -1384,19 +1399,19 @@ function enableDragToCreate(canvas, factory, options) {
1384
1399
  }
1385
1400
 
1386
1401
  // src/interactions/drawToCreate.ts
1387
- var import_fabric13 = require("fabric");
1402
+ var import_fabric14 = require("fabric");
1388
1403
 
1389
1404
  // src/shapes/rectangle.ts
1390
- var import_fabric10 = require("fabric");
1405
+ var import_fabric11 = require("fabric");
1391
1406
  function createRectangle(canvas, options) {
1392
- const rect = new import_fabric10.Rect({ ...DEFAULT_SHAPE_STYLE, ...options });
1407
+ const rect = new import_fabric11.Rect({ ...DEFAULT_SHAPE_STYLE, ...options });
1393
1408
  canvas.add(rect);
1394
1409
  canvas.requestRenderAll();
1395
1410
  return rect;
1396
1411
  }
1397
1412
  function createRectangleAtPoint(canvas, point, options) {
1398
1413
  const { width, height, ...style } = options;
1399
- const rect = new import_fabric10.Rect({
1414
+ const rect = new import_fabric11.Rect({
1400
1415
  ...DEFAULT_SHAPE_STYLE,
1401
1416
  left: point.x,
1402
1417
  top: point.y,
@@ -1415,7 +1430,7 @@ function editRectangle(canvas, rect, changes) {
1415
1430
  }
1416
1431
 
1417
1432
  // src/shapes/circle.ts
1418
- var import_fabric11 = require("fabric");
1433
+ var import_fabric12 = require("fabric");
1419
1434
  var CIRCLE_CONSTRAINTS = {
1420
1435
  lockRotation: true,
1421
1436
  lockUniScaling: true
@@ -1437,7 +1452,7 @@ function restoreCircleConstraints(rect) {
1437
1452
  }
1438
1453
  function createCircle(canvas, options) {
1439
1454
  const { size, ...rest } = options;
1440
- const rect = new import_fabric11.Rect({
1455
+ const rect = new import_fabric12.Rect({
1441
1456
  ...DEFAULT_CIRCLE_STYLE,
1442
1457
  ...CIRCLE_CONSTRAINTS,
1443
1458
  width: size,
@@ -1453,7 +1468,7 @@ function createCircle(canvas, options) {
1453
1468
  }
1454
1469
  function createCircleAtPoint(canvas, point, options) {
1455
1470
  const { size, ...style } = options;
1456
- const rect = new import_fabric11.Rect({
1471
+ const rect = new import_fabric12.Rect({
1457
1472
  ...DEFAULT_CIRCLE_STYLE,
1458
1473
  ...CIRCLE_CONSTRAINTS,
1459
1474
  left: point.x,
@@ -1487,17 +1502,17 @@ function editCircle(canvas, rect, changes) {
1487
1502
  }
1488
1503
 
1489
1504
  // src/shapes/polygon.ts
1490
- var import_fabric12 = require("fabric");
1505
+ var import_fabric13 = require("fabric");
1491
1506
  function createPolygon(canvas, options) {
1492
1507
  const { points, ...rest } = options;
1493
- const polygon = new import_fabric12.Polygon(points, { ...DEFAULT_SHAPE_STYLE, ...rest });
1508
+ const polygon = new import_fabric13.Polygon(points, { ...DEFAULT_SHAPE_STYLE, ...rest });
1494
1509
  canvas.add(polygon);
1495
1510
  canvas.requestRenderAll();
1496
1511
  return polygon;
1497
1512
  }
1498
1513
  function createPolygonAtPoint(canvas, point, options) {
1499
1514
  const { width, height, ...style } = options;
1500
- const polygon = new import_fabric12.Polygon(
1515
+ const polygon = new import_fabric13.Polygon(
1501
1516
  [
1502
1517
  { x: 0, y: 0 },
1503
1518
  { x: width, y: 0 },
@@ -1515,7 +1530,7 @@ function createPolygonFromDrag(canvas, start, end, style) {
1515
1530
  const height = Math.abs(end.y - start.y);
1516
1531
  const left = Math.min(start.x, end.x) + width / 2;
1517
1532
  const top = Math.min(start.y, end.y) + height / 2;
1518
- const polygon = new import_fabric12.Polygon(
1533
+ const polygon = new import_fabric13.Polygon(
1519
1534
  [
1520
1535
  { x: 0, y: 0 },
1521
1536
  { x: width, y: 0 },
@@ -1529,7 +1544,7 @@ function createPolygonFromDrag(canvas, start, end, style) {
1529
1544
  return polygon;
1530
1545
  }
1531
1546
  function createPolygonFromVertices(canvas, points, style) {
1532
- const polygon = new import_fabric12.Polygon(
1547
+ const polygon = new import_fabric13.Polygon(
1533
1548
  points.map((p) => ({ x: p.x, y: p.y })),
1534
1549
  { ...DEFAULT_SHAPE_STYLE, ...style }
1535
1550
  );
@@ -1607,7 +1622,7 @@ function enableDrawToCreate(canvas, options) {
1607
1622
  const snapping = createInteractionSnapping(
1608
1623
  canvas,
1609
1624
  options,
1610
- () => points.map((p) => new import_fabric13.Point(p.x, p.y))
1625
+ () => points.map((p) => new import_fabric14.Point(p.x, p.y))
1611
1626
  );
1612
1627
  function trackPreviewElement(obj) {
1613
1628
  snapping.excludeSet.add(obj);
@@ -1689,7 +1704,7 @@ function enableDrawToCreate(canvas, options) {
1689
1704
  canvas.selection = false;
1690
1705
  }
1691
1706
  points.push({ x, y });
1692
- const marker = new import_fabric13.Circle({
1707
+ const marker = new import_fabric14.Circle({
1693
1708
  left: x,
1694
1709
  top: y,
1695
1710
  radius: 4,
@@ -1705,7 +1720,7 @@ function enableDrawToCreate(canvas, options) {
1705
1720
  canvas.add(marker);
1706
1721
  if (points.length >= 2) {
1707
1722
  const prev = points[points.length - 2];
1708
- const edge = new import_fabric13.Line([prev.x, prev.y, x, y], lineStyle);
1723
+ const edge = new import_fabric14.Line([prev.x, prev.y, x, y], lineStyle);
1709
1724
  edgeLines.push(edge);
1710
1725
  trackPreviewElement(edge);
1711
1726
  canvas.add(edge);
@@ -1734,7 +1749,7 @@ function enableDrawToCreate(canvas, options) {
1734
1749
  untrackPreviewElement(trackingLine);
1735
1750
  canvas.remove(trackingLine);
1736
1751
  }
1737
- trackingLine = new import_fabric13.Line([lastPoint.x, lastPoint.y, x, y], {
1752
+ trackingLine = new import_fabric14.Line([lastPoint.x, lastPoint.y, x, y], {
1738
1753
  ...guideLineStyle,
1739
1754
  strokeDashArray: [5, 5]
1740
1755
  });
@@ -1746,7 +1761,7 @@ function enableDrawToCreate(canvas, options) {
1746
1761
  closingLine = null;
1747
1762
  }
1748
1763
  if (points.length >= 3) {
1749
- closingLine = new import_fabric13.Line([x, y, points[0].x, points[0].y], {
1764
+ closingLine = new import_fabric14.Line([x, y, points[0].x, points[0].y], {
1750
1765
  ...guideLineStyle,
1751
1766
  strokeDashArray: [5, 5]
1752
1767
  });
@@ -1788,30 +1803,30 @@ function enableDrawToCreate(canvas, options) {
1788
1803
  }
1789
1804
 
1790
1805
  // src/interactions/vertexEdit.ts
1791
- var import_fabric14 = require("fabric");
1806
+ var import_fabric15 = require("fabric");
1792
1807
  function localPointToScene(polygon, point) {
1793
1808
  const matrix = polygon.calcTransformMatrix();
1794
- const localPoint = new import_fabric14.Point(
1809
+ const localPoint = new import_fabric15.Point(
1795
1810
  point.x - polygon.pathOffset.x,
1796
1811
  point.y - polygon.pathOffset.y
1797
1812
  );
1798
- return import_fabric14.util.transformPoint(localPoint, matrix);
1813
+ return import_fabric15.util.transformPoint(localPoint, matrix);
1799
1814
  }
1800
1815
  function scenePointToLocal(polygon, scenePoint) {
1801
1816
  const matrix = polygon.calcTransformMatrix();
1802
- const invMatrix = import_fabric14.util.invertTransform(matrix);
1803
- const localPoint = import_fabric14.util.transformPoint(scenePoint, invMatrix);
1817
+ const invMatrix = import_fabric15.util.invertTransform(matrix);
1818
+ const localPoint = import_fabric15.util.transformPoint(scenePoint, invMatrix);
1804
1819
  return {
1805
1820
  x: localPoint.x + polygon.pathOffset.x,
1806
1821
  y: localPoint.y + polygon.pathOffset.y
1807
1822
  };
1808
1823
  }
1809
1824
  function sceneToScreen(scenePoint, canvas) {
1810
- return import_fabric14.util.transformPoint(scenePoint, canvas.viewportTransform);
1825
+ return import_fabric15.util.transformPoint(scenePoint, canvas.viewportTransform);
1811
1826
  }
1812
1827
  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);
1828
+ const inv = import_fabric15.util.invertTransform(canvas.viewportTransform);
1829
+ return import_fabric15.util.transformPoint(new import_fabric15.Point(screenX, screenY), inv);
1815
1830
  }
1816
1831
  function createHandleElement(radius, fill, stroke, strokeWidth) {
1817
1832
  const el = document.createElement("div");
@@ -1901,7 +1916,7 @@ function enableVertexEdit(canvas, polygon, options, onExit) {
1901
1916
  const canvasRelY = e.clientY - wrapperRect.top;
1902
1917
  const rawScene = screenToScene(canvasRelX, canvasRelY, canvas);
1903
1918
  const snapped = snapping.snapWithGuidelines(rawScene.x, rawScene.y);
1904
- const scenePoint = new import_fabric14.Point(snapped.x, snapped.y);
1919
+ const scenePoint = new import_fabric15.Point(snapped.x, snapped.y);
1905
1920
  const anchorIdx = i === 0 ? 1 : 0;
1906
1921
  const anchorBefore = localPointToScene(
1907
1922
  polygon,
@@ -1974,7 +1989,7 @@ function enableVertexEdit(canvas, polygon, options, onExit) {
1974
1989
  }
1975
1990
 
1976
1991
  // src/serialization.ts
1977
- var import_fabric15 = require("fabric");
1992
+ var import_fabric16 = require("fabric");
1978
1993
  var strokeBaseMap = /* @__PURE__ */ new WeakMap();
1979
1994
  function enableScaledStrokes(canvas) {
1980
1995
  function applyScaledStrokes() {
@@ -2031,11 +2046,18 @@ function serializeCanvas(canvas, options) {
2031
2046
  });
2032
2047
  return json;
2033
2048
  }
2034
- async function loadCanvas(canvas, json) {
2049
+ async function loadCanvas(canvas, json, options) {
2035
2050
  await canvas.loadFromJSON(json);
2051
+ if (options?.filter) {
2052
+ const toRemove = [];
2053
+ canvas.forEachObject((obj) => {
2054
+ if (!options.filter(obj)) toRemove.push(obj);
2055
+ });
2056
+ for (const obj of toRemove) canvas.remove(obj);
2057
+ }
2036
2058
  canvas.forEachObject((obj) => {
2037
2059
  obj.set(DEFAULT_CONTROL_STYLE);
2038
- if (obj.shapeType === "circle" && obj instanceof import_fabric15.Rect) {
2060
+ if (obj.shapeType === "circle" && obj instanceof import_fabric16.Rect) {
2039
2061
  restoreCircleConstraints(obj);
2040
2062
  }
2041
2063
  });
@@ -2055,6 +2077,7 @@ function useEditCanvas(options) {
2055
2077
  const [selected, setSelected] = (0, import_react2.useState)([]);
2056
2078
  const [viewportMode, setViewportModeState] = (0, import_react2.useState)("select");
2057
2079
  const [isEditingVertices, setIsEditingVertices] = (0, import_react2.useState)(false);
2080
+ const [isDirty, setIsDirty] = (0, import_react2.useState)(false);
2058
2081
  const setMode = (0, import_react2.useCallback)((setup) => {
2059
2082
  vertexEditCleanupRef.current?.();
2060
2083
  vertexEditCleanupRef.current = null;
@@ -2125,10 +2148,15 @@ function useEditCanvas(options) {
2125
2148
  canvas.on("selection:cleared", () => {
2126
2149
  setSelected([]);
2127
2150
  });
2151
+ if (options?.trackChanges) {
2152
+ canvas.on("object:added", () => setIsDirty(true));
2153
+ canvas.on("object:removed", () => setIsDirty(true));
2154
+ canvas.on("object:modified", () => setIsDirty(true));
2155
+ }
2128
2156
  if (options?.vertexEdit !== false) {
2129
2157
  const vertexOpts = typeof options?.vertexEdit === "object" ? options.vertexEdit : void 0;
2130
2158
  canvas.on("mouse:dblclick", (e) => {
2131
- if (e.target && e.target instanceof import_fabric16.Polygon) {
2159
+ if (e.target && e.target instanceof import_fabric17.Polygon) {
2132
2160
  vertexEditCleanupRef.current?.();
2133
2161
  vertexEditCleanupRef.current = enableVertexEdit(
2134
2162
  canvas,
@@ -2184,17 +2212,21 @@ function useEditCanvas(options) {
2184
2212
  viewportRef,
2185
2213
  setZoom
2186
2214
  );
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
- }, []);
2215
+ const setBackground = (0, import_react2.useCallback)(
2216
+ async (url, bgOpts) => {
2217
+ const canvas = canvasRef.current;
2218
+ if (!canvas) throw new Error("Canvas not ready");
2219
+ const resizeOpts = options?.backgroundResize !== false ? typeof options?.backgroundResize === "object" ? { ...options.backgroundResize, ...bgOpts } : { ...bgOpts } : bgOpts?.preserveOpacity ? { preserveOpacity: true } : void 0;
2220
+ const img = await setBackgroundImage(canvas, url, resizeOpts);
2221
+ if (options?.autoFitToBackground !== false) {
2222
+ fitViewportToBackground(canvas);
2223
+ syncZoom(canvasRef, setZoom);
2224
+ }
2225
+ return img;
2226
+ },
2227
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2228
+ []
2229
+ );
2198
2230
  return {
2199
2231
  /** Pass this to `<Canvas onReady={...} />` */
2200
2232
  onReady,
@@ -2239,21 +2271,28 @@ function useEditCanvas(options) {
2239
2271
  * Set a background image from a URL. Automatically resizes if the image
2240
2272
  * exceeds the configured limits (opt out via `backgroundResize: false`),
2241
2273
  * and fits the viewport after setting if `autoFitToBackground` is enabled.
2274
+ *
2275
+ * Pass `{ preserveOpacity: true }` to keep the current background opacity
2276
+ * when replacing the image.
2242
2277
  */
2243
- setBackground
2278
+ setBackground,
2279
+ /** Whether the canvas has been modified since the last `resetDirty()` call. Requires `trackChanges: true`. */
2280
+ isDirty,
2281
+ /** Reset the dirty flag (e.g., after a successful save). */
2282
+ resetDirty: (0, import_react2.useCallback)(() => setIsDirty(false), [])
2244
2283
  };
2245
2284
  }
2246
2285
 
2247
2286
  // src/hooks/useViewCanvas.ts
2248
2287
  var import_react3 = require("react");
2249
- var import_fabric17 = require("fabric");
2288
+ var import_fabric18 = require("fabric");
2250
2289
  var VIEW_BORDER_RADIUS = 4;
2251
2290
  function lockCanvas(canvas) {
2252
2291
  canvas.selection = false;
2253
2292
  canvas.forEachObject((obj) => {
2254
2293
  obj.selectable = false;
2255
2294
  obj.evented = false;
2256
- if (obj instanceof import_fabric17.Rect && obj.shapeType !== "circle" && obj.data?.type !== "DEVICE") {
2295
+ if (obj instanceof import_fabric18.Rect && obj.shapeType !== "circle" && obj.data?.type !== "DEVICE") {
2257
2296
  const rx = VIEW_BORDER_RADIUS / (obj.scaleX ?? 1);
2258
2297
  const ry = VIEW_BORDER_RADIUS / (obj.scaleY ?? 1);
2259
2298
  obj.set({ rx, ry });
@@ -2374,6 +2413,149 @@ function useViewCanvas(options) {
2374
2413
  setObjectStyleByType
2375
2414
  };
2376
2415
  }
2416
+
2417
+ // src/hooks/useCanvasEvents.ts
2418
+ var import_react4 = require("react");
2419
+ function useCanvasEvents(canvasRef, events) {
2420
+ const eventsRef = (0, import_react4.useRef)(events);
2421
+ eventsRef.current = events;
2422
+ (0, import_react4.useEffect)(() => {
2423
+ const canvas = canvasRef.current;
2424
+ if (!canvas) return;
2425
+ const wrappers = /* @__PURE__ */ new Map();
2426
+ for (const key of Object.keys(eventsRef.current)) {
2427
+ if (!eventsRef.current[key]) continue;
2428
+ const wrapped = (options) => {
2429
+ eventsRef.current[key]?.(options);
2430
+ };
2431
+ canvas.on(key, wrapped);
2432
+ wrappers.set(key, wrapped);
2433
+ }
2434
+ return () => {
2435
+ wrappers.forEach((handler, name) => {
2436
+ canvas.off(name, handler);
2437
+ });
2438
+ };
2439
+ }, [canvasRef]);
2440
+ }
2441
+
2442
+ // src/hooks/useCanvasTooltip.ts
2443
+ var import_react5 = require("react");
2444
+ function useCanvasTooltip(canvasRef, options) {
2445
+ const [state, setState] = (0, import_react5.useState)({
2446
+ visible: false,
2447
+ content: null,
2448
+ position: { x: 0, y: 0 }
2449
+ });
2450
+ const hoveredObjectRef = (0, import_react5.useRef)(null);
2451
+ const optionsRef = (0, import_react5.useRef)(options);
2452
+ optionsRef.current = options;
2453
+ (0, import_react5.useEffect)(() => {
2454
+ const canvas = canvasRef.current;
2455
+ if (!canvas) return;
2456
+ function calculatePosition(target) {
2457
+ const bounds = target.getBoundingRect();
2458
+ const zoom = canvas.getZoom();
2459
+ const vt = canvas.viewportTransform;
2460
+ if (!vt) return null;
2461
+ return {
2462
+ x: (bounds.left + bounds.width / 2) * zoom + vt[4],
2463
+ y: bounds.top * zoom + vt[5] - 10
2464
+ };
2465
+ }
2466
+ const handleMouseOver = (e) => {
2467
+ const target = e.target;
2468
+ if (!target) return;
2469
+ const content = optionsRef.current.getContent(target);
2470
+ if (content === null) return;
2471
+ if (hoveredObjectRef.current !== target) {
2472
+ hoveredObjectRef.current = target;
2473
+ const position = calculatePosition(target);
2474
+ if (position) {
2475
+ setState({ visible: true, content, position });
2476
+ }
2477
+ }
2478
+ };
2479
+ const handleMouseOut = (e) => {
2480
+ if (e.target && hoveredObjectRef.current === e.target) {
2481
+ setState((prev) => ({ ...prev, visible: false }));
2482
+ hoveredObjectRef.current = null;
2483
+ }
2484
+ };
2485
+ const updatePosition = () => {
2486
+ if (!hoveredObjectRef.current) return;
2487
+ const position = calculatePosition(hoveredObjectRef.current);
2488
+ if (position) {
2489
+ setState((prev) => prev.visible ? { ...prev, position } : prev);
2490
+ }
2491
+ };
2492
+ canvas.on("mouse:over", handleMouseOver);
2493
+ canvas.on("mouse:out", handleMouseOut);
2494
+ canvas.on("after:render", updatePosition);
2495
+ canvas.on("mouse:wheel", updatePosition);
2496
+ return () => {
2497
+ canvas.off("mouse:over", handleMouseOver);
2498
+ canvas.off("mouse:out", handleMouseOut);
2499
+ canvas.off("after:render", updatePosition);
2500
+ canvas.off("mouse:wheel", updatePosition);
2501
+ };
2502
+ }, [canvasRef]);
2503
+ return state;
2504
+ }
2505
+
2506
+ // src/hooks/useCanvasClick.ts
2507
+ var import_react6 = require("react");
2508
+ function useCanvasClick(canvasRef, onClick, options) {
2509
+ const onClickRef = (0, import_react6.useRef)(onClick);
2510
+ onClickRef.current = onClick;
2511
+ const optionsRef = (0, import_react6.useRef)(options);
2512
+ optionsRef.current = options;
2513
+ (0, import_react6.useEffect)(() => {
2514
+ const canvas = canvasRef.current;
2515
+ if (!canvas) return;
2516
+ let mouseDown = null;
2517
+ let isPanning = false;
2518
+ const handleMouseDown = (e) => {
2519
+ const native = e.e instanceof TouchEvent ? e.e.touches[0] : e.e;
2520
+ if (native) {
2521
+ mouseDown = { x: native.clientX, y: native.clientY, time: Date.now() };
2522
+ isPanning = false;
2523
+ }
2524
+ };
2525
+ const handleMouseMove = (e) => {
2526
+ if (!mouseDown) return;
2527
+ const native = e.e instanceof TouchEvent ? e.e.touches[0] : e.e;
2528
+ if (!native) return;
2529
+ const threshold = optionsRef.current?.threshold ?? 5;
2530
+ const deltaX = Math.abs(native.clientX - mouseDown.x);
2531
+ const deltaY = Math.abs(native.clientY - mouseDown.y);
2532
+ if (deltaX > threshold || deltaY > threshold) {
2533
+ isPanning = true;
2534
+ }
2535
+ };
2536
+ const handleMouseUp = (e) => {
2537
+ if (!mouseDown) return;
2538
+ const maxDuration = optionsRef.current?.maxDuration ?? 300;
2539
+ const elapsed = Date.now() - mouseDown.time;
2540
+ if (!isPanning && elapsed < maxDuration) {
2541
+ onClickRef.current(e.target);
2542
+ }
2543
+ mouseDown = null;
2544
+ isPanning = false;
2545
+ };
2546
+ canvas.on("mouse:down", handleMouseDown);
2547
+ canvas.on("mouse:move", handleMouseMove);
2548
+ canvas.on("mouse:up", handleMouseUp);
2549
+ return () => {
2550
+ canvas.off("mouse:down", handleMouseDown);
2551
+ canvas.off("mouse:move", handleMouseMove);
2552
+ canvas.off("mouse:up", handleMouseUp);
2553
+ };
2554
+ }, [canvasRef]);
2555
+ }
2556
+
2557
+ // src/index.ts
2558
+ var import_fabric19 = require("fabric");
2377
2559
  // Annotate the CommonJS export names for ESM import in node:
2378
2560
  0 && (module.exports = {
2379
2561
  Canvas,
@@ -2383,6 +2565,11 @@ function useViewCanvas(options) {
2383
2565
  DEFAULT_DRAG_SHAPE_STYLE,
2384
2566
  DEFAULT_GUIDELINE_SHAPE_STYLE,
2385
2567
  DEFAULT_SHAPE_STYLE,
2568
+ FabricCanvas,
2569
+ FabricImage,
2570
+ FabricObject,
2571
+ Polygon,
2572
+ Rect,
2386
2573
  createCircle,
2387
2574
  createCircleAtPoint,
2388
2575
  createPolygon,
@@ -2418,6 +2605,9 @@ function useViewCanvas(options) {
2418
2605
  setBackgroundInverted,
2419
2606
  setBackgroundOpacity,
2420
2607
  snapCursorPoint,
2608
+ useCanvasClick,
2609
+ useCanvasEvents,
2610
+ useCanvasTooltip,
2421
2611
  useEditCanvas,
2422
2612
  useViewCanvas
2423
2613
  });