@bwp-web/canvas 0.5.0 → 0.6.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 (40) hide show
  1. package/dist/Canvas/Canvas.d.ts +12 -1
  2. package/dist/Canvas/Canvas.d.ts.map +1 -1
  3. package/dist/background.d.ts.map +1 -1
  4. package/dist/constants.d.ts +2 -2
  5. package/dist/constants.d.ts.map +1 -1
  6. package/dist/fabricAugmentation.d.ts +3 -1
  7. package/dist/fabricAugmentation.d.ts.map +1 -1
  8. package/dist/history.d.ts +32 -0
  9. package/dist/history.d.ts.map +1 -0
  10. package/dist/hooks/shared.d.ts +6 -4
  11. package/dist/hooks/shared.d.ts.map +1 -1
  12. package/dist/hooks/useEditCanvas.d.ts +22 -0
  13. package/dist/hooks/useEditCanvas.d.ts.map +1 -1
  14. package/dist/hooks/useObjectOverlay.d.ts +4 -3
  15. package/dist/hooks/useObjectOverlay.d.ts.map +1 -1
  16. package/dist/hooks/useViewCanvas.d.ts +7 -0
  17. package/dist/hooks/useViewCanvas.d.ts.map +1 -1
  18. package/dist/index.cjs +416 -159
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.ts +8 -5
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +370 -112
  23. package/dist/index.js.map +1 -1
  24. package/dist/interactions/dragToCreate.d.ts +6 -1
  25. package/dist/interactions/dragToCreate.d.ts.map +1 -1
  26. package/dist/interactions/drawToCreate.d.ts +8 -3
  27. package/dist/interactions/drawToCreate.d.ts.map +1 -1
  28. package/dist/interactions/vertexEdit.d.ts +5 -1
  29. package/dist/interactions/vertexEdit.d.ts.map +1 -1
  30. package/dist/serialization.d.ts +25 -2
  31. package/dist/serialization.d.ts.map +1 -1
  32. package/dist/shapes/polygon.d.ts +2 -2
  33. package/dist/shapes/polygon.d.ts.map +1 -1
  34. package/dist/styles.d.ts +6 -0
  35. package/dist/styles.d.ts.map +1 -1
  36. package/dist/types.d.ts +8 -1
  37. package/dist/types.d.ts.map +1 -1
  38. package/dist/viewport.d.ts +12 -2
  39. package/dist/viewport.d.ts.map +1 -1
  40. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -27,14 +27,15 @@ __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
+ FabricCanvas: () => import_fabric19.Canvas,
31
+ FabricImage: () => import_fabric19.FabricImage,
32
+ FabricObject: () => import_fabric19.FabricObject,
33
+ Point: () => import_fabric19.Point,
34
+ Polygon: () => import_fabric19.Polygon,
35
+ Rect: () => import_fabric19.Rect,
36
36
  createCircle: () => createCircle,
37
37
  createCircleAtPoint: () => createCircleAtPoint,
38
+ createHistoryTracker: () => createHistoryTracker,
38
39
  createPolygon: () => createPolygon,
39
40
  createPolygonAtPoint: () => createPolygonAtPoint,
40
41
  createPolygonFromDrag: () => createPolygonFromDrag,
@@ -52,6 +53,7 @@ __export(index_exports, {
52
53
  enableObjectAlignment: () => enableObjectAlignment,
53
54
  enablePanAndZoom: () => enablePanAndZoom,
54
55
  enableRotationSnap: () => enableRotationSnap,
56
+ enableScaledBorderRadius: () => enableScaledBorderRadius,
55
57
  enableScaledStrokes: () => enableScaledStrokes,
56
58
  enableVertexEdit: () => enableVertexEdit,
57
59
  fitViewportToBackground: () => fitViewportToBackground,
@@ -75,7 +77,7 @@ __export(index_exports, {
75
77
  useEditCanvas: () => useEditCanvas,
76
78
  useObjectOverlay: () => useObjectOverlay,
77
79
  useViewCanvas: () => useViewCanvas,
78
- util: () => import_fabric20.util
80
+ util: () => import_fabric19.util
79
81
  });
80
82
  module.exports = __toCommonJS(index_exports);
81
83
 
@@ -114,7 +116,9 @@ function Canvas({
114
116
  height,
115
117
  className,
116
118
  style,
117
- onReady
119
+ onReady,
120
+ keyboardShortcuts,
121
+ fabricOptions
118
122
  }) {
119
123
  const canvasRef = (0, import_react.useRef)(null);
120
124
  const wrapperRef = (0, import_react.useRef)(null);
@@ -126,11 +130,12 @@ function Canvas({
126
130
  const initialWidth = isFixedSize ? width : wrapper.clientWidth || 800;
127
131
  const initialHeight = isFixedSize ? height : wrapper.clientHeight || 600;
128
132
  const fabricCanvas = new import_fabric2.Canvas(el, {
133
+ ...fabricOptions,
129
134
  width: initialWidth,
130
135
  height: initialHeight
131
136
  });
132
137
  onReady?.(fabricCanvas);
133
- const cleanupShortcuts = enableKeyboardShortcuts(fabricCanvas);
138
+ const cleanupShortcuts = keyboardShortcuts ? enableKeyboardShortcuts(fabricCanvas) : void 0;
134
139
  let observer;
135
140
  let rafId = 0;
136
141
  if (!isFixedSize) {
@@ -154,7 +159,7 @@ function Canvas({
154
159
  return () => {
155
160
  cancelAnimationFrame(rafId);
156
161
  observer?.disconnect();
157
- cleanupShortcuts();
162
+ cleanupShortcuts?.();
158
163
  fabricCanvas.dispose();
159
164
  };
160
165
  }, []);
@@ -163,7 +168,7 @@ function Canvas({
163
168
  }
164
169
 
165
170
  // src/hooks/useEditCanvas.ts
166
- var import_react2 = require("react");
171
+ var import_react3 = require("react");
167
172
  var import_fabric17 = require("fabric");
168
173
 
169
174
  // src/viewport.ts
@@ -172,7 +177,7 @@ var import_fabric3 = require("fabric");
172
177
  // src/constants.ts
173
178
  var DEFAULT_MIN_ZOOM = 0.2;
174
179
  var DEFAULT_MAX_ZOOM = 10;
175
- var DEFAULT_ZOOM_FACTOR = 1.03;
180
+ var DEFAULT_ZOOM_FACTOR = 0.999;
176
181
  var DEFAULT_ZOOM_STEP = 1.2;
177
182
  var DEFAULT_VIEWPORT_PADDING = 0.05;
178
183
  var BASE_CANVAS_SIZE = 1e3;
@@ -215,7 +220,7 @@ function setupWheelZoom(canvas, bounds, zoomFactor, isEnabled) {
215
220
  e.stopPropagation();
216
221
  const delta = e.deltaY;
217
222
  let zoom = canvas.getZoom();
218
- zoom = delta < 0 ? zoom * zoomFactor : zoom / zoomFactor;
223
+ zoom *= zoomFactor ** delta;
219
224
  zoom = Math.min(Math.max(zoom, bounds.minZoom), bounds.maxZoom);
220
225
  canvas.zoomToPoint(new import_fabric3.Point(e.offsetX, e.offsetY), zoom);
221
226
  };
@@ -323,8 +328,15 @@ function enablePanAndZoom(canvas, options) {
323
328
  const zoomFactor = options?.zoomFactor ?? DEFAULT_ZOOM_FACTOR;
324
329
  let mode = options?.initialMode ?? "select";
325
330
  let enabled = true;
331
+ let currentAnimRafId = null;
326
332
  const isEnabled = () => enabled;
327
333
  const getMode = () => mode;
334
+ function cancelAnimation() {
335
+ if (currentAnimRafId !== null) {
336
+ cancelAnimationFrame(currentAnimRafId);
337
+ currentAnimRafId = null;
338
+ }
339
+ }
328
340
  const handleWheel = setupWheelZoom(canvas, bounds, zoomFactor, isEnabled);
329
341
  const panHandlers = setupMousePan(canvas, getMode, isEnabled);
330
342
  const cleanupPinch = setupPinchZoom(canvas, bounds, isEnabled);
@@ -370,6 +382,7 @@ function enablePanAndZoom(canvas, options) {
370
382
  );
371
383
  },
372
384
  panToObject(object, panOpts) {
385
+ cancelAnimation();
373
386
  const zoom = canvas.getZoom();
374
387
  const objectCenter = object.getCenterPoint();
375
388
  const canvasCenterX = canvas.getWidth() / 2;
@@ -412,12 +425,37 @@ function enablePanAndZoom(canvas, options) {
412
425
  currentY
413
426
  ]);
414
427
  if (t < 1) {
415
- requestAnimationFrame(step);
428
+ currentAnimRafId = requestAnimationFrame(step);
429
+ } else {
430
+ currentAnimRafId = null;
416
431
  }
417
432
  }
418
- requestAnimationFrame(step);
433
+ currentAnimRafId = requestAnimationFrame(step);
434
+ },
435
+ zoomToFit(object, fitOpts) {
436
+ cancelAnimation();
437
+ const padding = fitOpts?.padding ?? 0.1;
438
+ const objWidth = (object.width ?? 0) * (object.scaleX ?? 1);
439
+ const objHeight = (object.height ?? 0) * (object.scaleY ?? 1);
440
+ if (!objWidth || !objHeight) return;
441
+ const canvasWidth = canvas.getWidth();
442
+ const canvasHeight = canvas.getHeight();
443
+ const availableWidth = canvasWidth * (1 - padding * 2);
444
+ const availableHeight = canvasHeight * (1 - padding * 2);
445
+ const zoom = Math.min(
446
+ Math.max(
447
+ Math.min(availableWidth / objWidth, availableHeight / objHeight),
448
+ bounds.minZoom
449
+ ),
450
+ bounds.maxZoom
451
+ );
452
+ const objectCenter = object.getCenterPoint();
453
+ const offsetX = canvasWidth / 2 - objectCenter.x * zoom;
454
+ const offsetY = canvasHeight / 2 - objectCenter.y * zoom;
455
+ canvas.setViewportTransform([zoom, 0, 0, zoom, offsetX, offsetY]);
419
456
  },
420
457
  cleanup() {
458
+ cancelAnimation();
421
459
  canvas.off("mouse:wheel", handleWheel);
422
460
  canvas.off("mouse:down", panHandlers.handleMouseDown);
423
461
  canvas.off("mouse:move", panHandlers.handleMouseMove);
@@ -430,6 +468,9 @@ function resetViewport(canvas) {
430
468
  canvas.setViewportTransform([1, 0, 0, 1, 0, 0]);
431
469
  }
432
470
 
471
+ // src/hooks/shared.ts
472
+ var import_react2 = require("react");
473
+
433
474
  // src/background.ts
434
475
  var import_fabric4 = require("fabric");
435
476
  function getBackgroundImage(canvas) {
@@ -560,7 +601,8 @@ function resizeImageUrl(url, options) {
560
601
  async function setBackgroundImage(canvas, url, options) {
561
602
  const prevContrast = options?.preserveContrast ? getBackgroundContrast(canvas) : void 0;
562
603
  let imageUrl = url;
563
- if (options !== void 0) {
604
+ const hasResizeOptions = options?.maxSize !== void 0 || options?.minSize !== void 0;
605
+ if (hasResizeOptions) {
564
606
  const result = await resizeImageUrl(url, options);
565
607
  imageUrl = result.url;
566
608
  }
@@ -578,29 +620,35 @@ function syncZoom(canvasRef, setZoom) {
578
620
  const canvas = canvasRef.current;
579
621
  if (canvas) setZoom(canvas.getZoom());
580
622
  }
581
- function createViewportActions(canvasRef, viewportRef, setZoom) {
582
- const resetViewport2 = () => {
583
- const canvas = canvasRef.current;
584
- if (!canvas) return;
585
- if (canvas.backgroundImage) {
586
- fitViewportToBackground(canvas);
587
- } else {
588
- resetViewport(canvas);
589
- }
590
- setZoom(canvas.getZoom());
591
- };
592
- const zoomIn = (step) => {
593
- viewportRef.current?.zoomIn(step);
594
- syncZoom(canvasRef, setZoom);
595
- };
596
- const zoomOut = (step) => {
597
- viewportRef.current?.zoomOut(step);
598
- syncZoom(canvasRef, setZoom);
599
- };
600
- const panToObject = (object, panOpts) => {
601
- viewportRef.current?.panToObject(object, panOpts);
602
- };
603
- return { resetViewport: resetViewport2, zoomIn, zoomOut, panToObject };
623
+ function useViewportActions(canvasRef, viewportRef, setZoom) {
624
+ return (0, import_react2.useMemo)(() => {
625
+ const resetViewport2 = () => {
626
+ const canvas = canvasRef.current;
627
+ if (!canvas) return;
628
+ if (canvas.backgroundImage) {
629
+ fitViewportToBackground(canvas);
630
+ } else {
631
+ resetViewport(canvas);
632
+ }
633
+ setZoom(canvas.getZoom());
634
+ };
635
+ const zoomIn = (step) => {
636
+ viewportRef.current?.zoomIn(step);
637
+ syncZoom(canvasRef, setZoom);
638
+ };
639
+ const zoomOut = (step) => {
640
+ viewportRef.current?.zoomOut(step);
641
+ syncZoom(canvasRef, setZoom);
642
+ };
643
+ const panToObject = (object, panOpts) => {
644
+ viewportRef.current?.panToObject(object, panOpts);
645
+ };
646
+ const zoomToFit = (object, fitOpts) => {
647
+ viewportRef.current?.zoomToFit(object, fitOpts);
648
+ syncZoom(canvasRef, setZoom);
649
+ };
650
+ return { resetViewport: resetViewport2, zoomIn, zoomOut, panToObject, zoomToFit };
651
+ }, []);
604
652
  }
605
653
  function resolveAlignmentEnabled(enableAlignment, alignmentProp) {
606
654
  if (enableAlignment !== void 0) return enableAlignment;
@@ -1485,6 +1533,11 @@ function enableDragToCreate(canvas, factory, options) {
1485
1533
  if (width < MIN_DRAG_SIZE && height < MIN_DRAG_SIZE) {
1486
1534
  canvas.requestRenderAll();
1487
1535
  previewRect = null;
1536
+ if (options?.clickFactory) {
1537
+ const obj2 = options.clickFactory(canvas, { x: startX, y: startY });
1538
+ restoreViewport(options?.viewport);
1539
+ options?.onCreated?.(obj2);
1540
+ }
1488
1541
  return;
1489
1542
  }
1490
1543
  const obj = factory(canvas, { startX, startY, width, height });
@@ -1654,7 +1707,7 @@ function createPolygonAtPoint(canvas, point, options) {
1654
1707
  canvas.requestRenderAll();
1655
1708
  return polygon;
1656
1709
  }
1657
- function createPolygonFromDrag(canvas, start, end, style) {
1710
+ function createPolygonFromDrag(canvas, start, end, options) {
1658
1711
  const width = Math.abs(end.x - start.x);
1659
1712
  const height = Math.abs(end.y - start.y);
1660
1713
  const left = Math.min(start.x, end.x) + width / 2;
@@ -1666,16 +1719,16 @@ function createPolygonFromDrag(canvas, start, end, style) {
1666
1719
  { x: width, y: height },
1667
1720
  { x: 0, y: height }
1668
1721
  ],
1669
- { ...DEFAULT_SHAPE_STYLE, left, top, ...style }
1722
+ { ...DEFAULT_SHAPE_STYLE, left, top, ...options }
1670
1723
  );
1671
1724
  canvas.add(polygon);
1672
1725
  canvas.requestRenderAll();
1673
1726
  return polygon;
1674
1727
  }
1675
- function createPolygonFromVertices(canvas, points, style) {
1728
+ function createPolygonFromVertices(canvas, points, options) {
1676
1729
  const polygon = new import_fabric13.Polygon(
1677
1730
  points.map((p) => ({ x: p.x, y: p.y })),
1678
- { ...DEFAULT_SHAPE_STYLE, ...style }
1731
+ { ...DEFAULT_SHAPE_STYLE, ...options }
1679
1732
  );
1680
1733
  canvas.add(polygon);
1681
1734
  canvas.requestRenderAll();
@@ -1799,14 +1852,14 @@ function enableDrawToCreate(canvas, options) {
1799
1852
  const finalize = () => {
1800
1853
  removePreviewElements();
1801
1854
  snapping.clearSnapResult();
1802
- const polygon = createPolygonFromVertices(canvas, points, options?.style);
1855
+ const obj = options?.factory ? options.factory(canvas, [...points]) : createPolygonFromVertices(canvas, points, options?.style);
1803
1856
  if (options?.data) {
1804
- polygon.data = options.data;
1857
+ obj.data = options.data;
1805
1858
  }
1806
1859
  canvas.selection = previousSelection;
1807
1860
  canvas.requestRenderAll();
1808
1861
  restoreViewport(options?.viewport);
1809
- options?.onCreated?.(polygon);
1862
+ options?.onCreated?.(obj);
1810
1863
  points.length = 0;
1811
1864
  };
1812
1865
  const handleMouseDown = (event) => {
@@ -2115,7 +2168,7 @@ function enableVertexEdit(canvas, polygon, options, onExit) {
2115
2168
  });
2116
2169
  canvas.discardActiveObject();
2117
2170
  canvas.requestRenderAll();
2118
- onExit?.();
2171
+ (options?.onExit ?? onExit)?.();
2119
2172
  }
2120
2173
  return cleanup;
2121
2174
  }
@@ -2123,6 +2176,8 @@ function enableVertexEdit(canvas, polygon, options, onExit) {
2123
2176
  // src/serialization.ts
2124
2177
  var import_fabric16 = require("fabric");
2125
2178
  var strokeBaseMap = /* @__PURE__ */ new WeakMap();
2179
+ var borderRadiusBaseMap = /* @__PURE__ */ new WeakMap();
2180
+ var DEFAULT_VIEW_BORDER_RADIUS = 4;
2126
2181
  function enableScaledStrokes(canvas) {
2127
2182
  function applyScaledStrokes() {
2128
2183
  const zoom = canvas.getZoom();
@@ -2147,6 +2202,29 @@ function enableScaledStrokes(canvas) {
2147
2202
  });
2148
2203
  };
2149
2204
  }
2205
+ function enableScaledBorderRadius(canvas, options) {
2206
+ const radius = options?.radius ?? DEFAULT_VIEW_BORDER_RADIUS;
2207
+ function applyScaledBorderRadius() {
2208
+ canvas.forEachObject((obj) => {
2209
+ if (!(obj instanceof import_fabric16.Rect)) return;
2210
+ if (!borderRadiusBaseMap.has(obj)) return;
2211
+ const rx = radius / (obj.scaleX ?? 1);
2212
+ const ry = radius / (obj.scaleY ?? 1);
2213
+ obj.set({ rx, ry });
2214
+ });
2215
+ }
2216
+ canvas.on("before:render", applyScaledBorderRadius);
2217
+ return () => {
2218
+ canvas.off("before:render", applyScaledBorderRadius);
2219
+ canvas.forEachObject((obj) => {
2220
+ if (!(obj instanceof import_fabric16.Rect)) return;
2221
+ const base = borderRadiusBaseMap.get(obj);
2222
+ if (base !== void 0) {
2223
+ obj.set({ rx: base.rx, ry: base.ry });
2224
+ }
2225
+ });
2226
+ };
2227
+ }
2150
2228
  function getBaseStrokeWidth(obj) {
2151
2229
  return strokeBaseMap.get(obj) ?? obj.strokeWidth ?? 0;
2152
2230
  }
@@ -2172,15 +2250,42 @@ function serializeCanvas(canvas, options) {
2172
2250
  obj.strokeWidth = base;
2173
2251
  }
2174
2252
  });
2253
+ const appliedRadii = /* @__PURE__ */ new Map();
2254
+ canvas.forEachObject((obj) => {
2255
+ if (!(obj instanceof import_fabric16.Rect)) return;
2256
+ const base = borderRadiusBaseMap.get(obj);
2257
+ if (base !== void 0) {
2258
+ appliedRadii.set(obj, { rx: obj.rx ?? 0, ry: obj.ry ?? 0 });
2259
+ obj.set({ rx: base.rx, ry: base.ry });
2260
+ }
2261
+ });
2175
2262
  const json = canvas.toObject(properties);
2176
2263
  delete json.backgroundColor;
2177
2264
  scaledWidths.forEach((scaled, obj) => {
2178
2265
  obj.strokeWidth = scaled;
2179
2266
  });
2267
+ appliedRadii.forEach((radii, obj) => {
2268
+ obj.set({ rx: radii.rx, ry: radii.ry });
2269
+ });
2180
2270
  return json;
2181
2271
  }
2182
2272
  async function loadCanvas(canvas, json, options) {
2183
2273
  await canvas.loadFromJSON(json);
2274
+ canvas.backgroundColor = "";
2275
+ delete canvas.backgroundFilters;
2276
+ const bg = canvas.backgroundImage;
2277
+ if (bg instanceof import_fabric16.FabricImage) {
2278
+ if (bg.originX !== "center" || bg.originY !== "center") {
2279
+ const center = bg.getCenterPoint();
2280
+ bg.set({
2281
+ originX: "center",
2282
+ originY: "center",
2283
+ left: center.x,
2284
+ top: center.y
2285
+ });
2286
+ bg.setCoords();
2287
+ }
2288
+ }
2184
2289
  if (options?.filter) {
2185
2290
  const toRemove = [];
2186
2291
  canvas.forEachObject((obj) => {
@@ -2200,30 +2305,130 @@ async function loadCanvas(canvas, json, options) {
2200
2305
  obj.setCoords();
2201
2306
  });
2202
2307
  canvas.forEachObject((obj) => {
2308
+ const data = obj.data;
2309
+ if (data?.strokeWidthBase !== void 0) {
2310
+ delete data.strokeWidthBase;
2311
+ }
2203
2312
  obj.set(DEFAULT_CONTROL_STYLE);
2204
2313
  if (obj.shapeType === "circle" && obj instanceof import_fabric16.Rect) {
2205
2314
  restoreCircleConstraints(obj);
2206
2315
  }
2316
+ const borderRadius = options?.borderRadius ?? DEFAULT_VIEW_BORDER_RADIUS;
2317
+ if (borderRadius !== false && obj instanceof import_fabric16.Rect && obj.shapeType !== "circle" && obj.data?.type !== "DEVICE") {
2318
+ borderRadiusBaseMap.set(obj, { rx: obj.rx ?? 0, ry: obj.ry ?? 0 });
2319
+ const rx = borderRadius / (obj.scaleX ?? 1);
2320
+ const ry = borderRadius / (obj.scaleY ?? 1);
2321
+ obj.set({ rx, ry });
2322
+ }
2207
2323
  });
2208
2324
  canvas.requestRenderAll();
2209
2325
  return canvas.getObjects();
2210
2326
  }
2211
2327
 
2328
+ // src/history.ts
2329
+ function createHistoryTracker(canvas, options) {
2330
+ const maxSize = options?.maxSize ?? 50;
2331
+ const debounceMs = options?.debounce ?? 300;
2332
+ const snapshots = [];
2333
+ let currentIndex = -1;
2334
+ let isUndoRedo = false;
2335
+ let debounceTimer = null;
2336
+ function captureSnapshot() {
2337
+ if (isUndoRedo) return;
2338
+ const snapshot = serializeCanvas(canvas);
2339
+ if (currentIndex < snapshots.length - 1) {
2340
+ snapshots.length = currentIndex + 1;
2341
+ }
2342
+ snapshots.push(snapshot);
2343
+ if (snapshots.length > maxSize) {
2344
+ snapshots.shift();
2345
+ }
2346
+ currentIndex = snapshots.length - 1;
2347
+ }
2348
+ function debouncedCapture() {
2349
+ if (debounceTimer !== null) {
2350
+ clearTimeout(debounceTimer);
2351
+ }
2352
+ debounceTimer = setTimeout(() => {
2353
+ debounceTimer = null;
2354
+ captureSnapshot();
2355
+ }, debounceMs);
2356
+ }
2357
+ const onChange = () => {
2358
+ if (!isUndoRedo) debouncedCapture();
2359
+ };
2360
+ canvas.on("object:added", onChange);
2361
+ canvas.on("object:modified", onChange);
2362
+ canvas.on("object:removed", onChange);
2363
+ async function loadSnapshot(index) {
2364
+ if (index < 0 || index >= snapshots.length) return;
2365
+ isUndoRedo = true;
2366
+ currentIndex = index;
2367
+ try {
2368
+ await loadCanvas(canvas, snapshots[index]);
2369
+ } finally {
2370
+ isUndoRedo = false;
2371
+ }
2372
+ }
2373
+ return {
2374
+ async undo() {
2375
+ if (currentIndex <= 0) return;
2376
+ await loadSnapshot(currentIndex - 1);
2377
+ },
2378
+ async redo() {
2379
+ if (currentIndex >= snapshots.length - 1) return;
2380
+ await loadSnapshot(currentIndex + 1);
2381
+ },
2382
+ canUndo() {
2383
+ return currentIndex > 0;
2384
+ },
2385
+ canRedo() {
2386
+ return currentIndex < snapshots.length - 1;
2387
+ },
2388
+ pushSnapshot() {
2389
+ if (debounceTimer !== null) {
2390
+ clearTimeout(debounceTimer);
2391
+ debounceTimer = null;
2392
+ }
2393
+ captureSnapshot();
2394
+ },
2395
+ cleanup() {
2396
+ if (debounceTimer !== null) {
2397
+ clearTimeout(debounceTimer);
2398
+ debounceTimer = null;
2399
+ }
2400
+ canvas.off("object:added", onChange);
2401
+ canvas.off("object:modified", onChange);
2402
+ canvas.off("object:removed", onChange);
2403
+ snapshots.length = 0;
2404
+ currentIndex = -1;
2405
+ }
2406
+ };
2407
+ }
2408
+
2212
2409
  // src/hooks/useEditCanvas.ts
2213
2410
  function useEditCanvas(options) {
2214
- const canvasRef = (0, import_react2.useRef)(null);
2215
- const viewportRef = (0, import_react2.useRef)(null);
2216
- const alignmentCleanupRef = (0, import_react2.useRef)(null);
2217
- const rotationSnapCleanupRef = (0, import_react2.useRef)(null);
2218
- const modeCleanupRef = (0, import_react2.useRef)(null);
2219
- const vertexEditCleanupRef = (0, import_react2.useRef)(null);
2220
- const keyboardCleanupRef = (0, import_react2.useRef)(null);
2221
- const [zoom, setZoom] = (0, import_react2.useState)(1);
2222
- const [selected, setSelected] = (0, import_react2.useState)([]);
2223
- const [viewportMode, setViewportModeState] = (0, import_react2.useState)("select");
2224
- const [isEditingVertices, setIsEditingVertices] = (0, import_react2.useState)(false);
2225
- const [isDirty, setIsDirty] = (0, import_react2.useState)(false);
2226
- const setMode = (0, import_react2.useCallback)((setup) => {
2411
+ const canvasRef = (0, import_react3.useRef)(null);
2412
+ const viewportRef = (0, import_react3.useRef)(null);
2413
+ const alignmentCleanupRef = (0, import_react3.useRef)(null);
2414
+ const rotationSnapCleanupRef = (0, import_react3.useRef)(null);
2415
+ const modeCleanupRef = (0, import_react3.useRef)(null);
2416
+ const vertexEditCleanupRef = (0, import_react3.useRef)(null);
2417
+ const keyboardCleanupRef = (0, import_react3.useRef)(null);
2418
+ const historyRef = (0, import_react3.useRef)(null);
2419
+ const optionsRef = (0, import_react3.useRef)(options);
2420
+ optionsRef.current = options;
2421
+ const savedSelectabilityRef = (0, import_react3.useRef)(
2422
+ /* @__PURE__ */ new WeakMap()
2423
+ );
2424
+ const [zoom, setZoom] = (0, import_react3.useState)(1);
2425
+ const [selected, setSelected] = (0, import_react3.useState)([]);
2426
+ const [viewportMode, setViewportModeState] = (0, import_react3.useState)("select");
2427
+ const [isEditingVertices, setIsEditingVertices] = (0, import_react3.useState)(false);
2428
+ const [isDirty, setIsDirty] = (0, import_react3.useState)(false);
2429
+ const [canUndo, setCanUndo] = (0, import_react3.useState)(false);
2430
+ const [canRedo, setCanRedo] = (0, import_react3.useState)(false);
2431
+ const setMode = (0, import_react3.useCallback)((setup) => {
2227
2432
  vertexEditCleanupRef.current?.();
2228
2433
  vertexEditCleanupRef.current = null;
2229
2434
  setIsEditingVertices(false);
@@ -2234,10 +2439,17 @@ function useEditCanvas(options) {
2234
2439
  if (setup === null) {
2235
2440
  canvas.selection = true;
2236
2441
  canvas.forEachObject((obj) => {
2237
- obj.selectable = true;
2238
- obj.evented = true;
2442
+ const saved = savedSelectabilityRef.current.get(obj);
2443
+ obj.selectable = saved?.selectable ?? true;
2444
+ obj.evented = saved?.evented ?? true;
2239
2445
  });
2240
2446
  } else {
2447
+ canvas.forEachObject((obj) => {
2448
+ savedSelectabilityRef.current.set(obj, {
2449
+ selectable: obj.selectable,
2450
+ evented: obj.evented
2451
+ });
2452
+ });
2241
2453
  canvas.selection = false;
2242
2454
  canvas.forEachObject((obj) => {
2243
2455
  obj.selectable = false;
@@ -2249,36 +2461,41 @@ function useEditCanvas(options) {
2249
2461
  }
2250
2462
  }
2251
2463
  }, []);
2252
- const onReady = (0, import_react2.useCallback)(
2464
+ const onReady = (0, import_react3.useCallback)(
2253
2465
  (canvas) => {
2254
2466
  canvasRef.current = canvas;
2255
- if (options?.scaledStrokes !== false) {
2467
+ const opts = optionsRef.current;
2468
+ if (opts?.scaledStrokes !== false) {
2256
2469
  enableScaledStrokes(canvas);
2257
2470
  }
2258
- if (options?.keyboardShortcuts !== false) {
2471
+ if (opts?.borderRadius !== false) {
2472
+ const borderRadiusOpts = typeof opts?.borderRadius === "number" ? { radius: opts.borderRadius } : void 0;
2473
+ enableScaledBorderRadius(canvas, borderRadiusOpts);
2474
+ }
2475
+ if (opts?.keyboardShortcuts !== false) {
2259
2476
  keyboardCleanupRef.current = enableKeyboardShortcuts(canvas);
2260
2477
  }
2261
- setCanvasAlignmentEnabled(canvas, options?.enableAlignment);
2262
- if (options?.panAndZoom !== false) {
2478
+ setCanvasAlignmentEnabled(canvas, opts?.enableAlignment);
2479
+ if (opts?.panAndZoom !== false) {
2263
2480
  viewportRef.current = enablePanAndZoom(
2264
2481
  canvas,
2265
- typeof options?.panAndZoom === "object" ? options.panAndZoom : void 0
2482
+ typeof opts?.panAndZoom === "object" ? opts.panAndZoom : void 0
2266
2483
  );
2267
2484
  }
2268
2485
  const alignmentEnabled = resolveAlignmentEnabled(
2269
- options?.enableAlignment,
2270
- options?.alignment
2486
+ opts?.enableAlignment,
2487
+ opts?.alignment
2271
2488
  );
2272
2489
  if (alignmentEnabled) {
2273
2490
  alignmentCleanupRef.current = enableObjectAlignment(
2274
2491
  canvas,
2275
- typeof options?.alignment === "object" ? options.alignment : void 0
2492
+ typeof opts?.alignment === "object" ? opts.alignment : void 0
2276
2493
  );
2277
2494
  }
2278
- if (options?.rotationSnap !== false) {
2495
+ if (opts?.rotationSnap !== false) {
2279
2496
  rotationSnapCleanupRef.current = enableRotationSnap(
2280
2497
  canvas,
2281
- typeof options?.rotationSnap === "object" ? options.rotationSnap : void 0
2498
+ typeof opts?.rotationSnap === "object" ? opts.rotationSnap : void 0
2282
2499
  );
2283
2500
  }
2284
2501
  canvas.on("mouse:wheel", () => {
@@ -2293,44 +2510,63 @@ function useEditCanvas(options) {
2293
2510
  canvas.on("selection:cleared", () => {
2294
2511
  setSelected([]);
2295
2512
  });
2296
- if (options?.trackChanges) {
2513
+ if (opts?.trackChanges) {
2297
2514
  canvas.on("object:added", () => setIsDirty(true));
2298
2515
  canvas.on("object:removed", () => setIsDirty(true));
2299
2516
  canvas.on("object:modified", () => setIsDirty(true));
2300
2517
  }
2301
- if (options?.vertexEdit !== false) {
2302
- const vertexOpts = typeof options?.vertexEdit === "object" ? options.vertexEdit : void 0;
2518
+ if (opts?.history) {
2519
+ const syncHistoryState = () => {
2520
+ const h = historyRef.current;
2521
+ if (!h) return;
2522
+ setTimeout(() => {
2523
+ setCanUndo(h.canUndo());
2524
+ setCanRedo(h.canRedo());
2525
+ }, 350);
2526
+ };
2527
+ canvas.on("object:added", syncHistoryState);
2528
+ canvas.on("object:removed", syncHistoryState);
2529
+ canvas.on("object:modified", syncHistoryState);
2530
+ }
2531
+ if (opts?.vertexEdit !== false) {
2532
+ const vertexOpts = typeof opts?.vertexEdit === "object" ? opts.vertexEdit : void 0;
2303
2533
  canvas.on("mouse:dblclick", (e) => {
2304
2534
  if (e.target && e.target instanceof import_fabric17.Polygon) {
2305
2535
  vertexEditCleanupRef.current?.();
2306
- vertexEditCleanupRef.current = enableVertexEdit(
2307
- canvas,
2308
- e.target,
2309
- vertexOpts,
2310
- () => {
2536
+ vertexEditCleanupRef.current = enableVertexEdit(canvas, e.target, {
2537
+ ...vertexOpts,
2538
+ onExit: () => {
2311
2539
  vertexEditCleanupRef.current = null;
2312
2540
  setIsEditingVertices(false);
2313
2541
  }
2314
- );
2542
+ });
2315
2543
  setIsEditingVertices(true);
2316
2544
  }
2317
2545
  });
2318
2546
  }
2319
- const onReadyResult = options?.onReady?.(canvas);
2320
- if (options?.autoFitToBackground !== false) {
2547
+ if (opts?.history) {
2548
+ const historyOpts = typeof opts.history === "object" ? opts.history : void 0;
2549
+ historyRef.current = createHistoryTracker(canvas, historyOpts);
2550
+ }
2551
+ const onReadyResult = opts?.onReady?.(canvas);
2552
+ if (opts?.autoFitToBackground !== false) {
2321
2553
  Promise.resolve(onReadyResult).then(() => {
2322
2554
  if (canvas.backgroundImage) {
2323
2555
  fitViewportToBackground(canvas);
2324
2556
  syncZoom(canvasRef, setZoom);
2325
2557
  }
2558
+ historyRef.current?.pushSnapshot();
2559
+ });
2560
+ } else {
2561
+ Promise.resolve(onReadyResult).then(() => {
2562
+ historyRef.current?.pushSnapshot();
2326
2563
  });
2327
2564
  }
2328
2565
  },
2329
2566
  // onReady and panAndZoom are intentionally excluded — we only initialize once
2330
- // eslint-disable-next-line react-hooks/exhaustive-deps
2331
2567
  []
2332
2568
  );
2333
- (0, import_react2.useEffect)(() => {
2569
+ (0, import_react3.useEffect)(() => {
2334
2570
  const canvas = canvasRef.current;
2335
2571
  if (!canvas) return;
2336
2572
  setCanvasAlignmentEnabled(canvas, options?.enableAlignment);
@@ -2348,28 +2584,24 @@ function useEditCanvas(options) {
2348
2584
  alignmentCleanupRef.current = null;
2349
2585
  }
2350
2586
  }, [options?.enableAlignment]);
2351
- const setViewportMode = (0, import_react2.useCallback)((mode) => {
2587
+ const setViewportMode = (0, import_react3.useCallback)((mode) => {
2352
2588
  viewportRef.current?.setMode(mode);
2353
2589
  setViewportModeState(mode);
2354
2590
  }, []);
2355
- const { resetViewport: resetViewport2, zoomIn, zoomOut, panToObject } = createViewportActions(
2356
- canvasRef,
2357
- viewportRef,
2358
- setZoom
2359
- );
2360
- const setBackground = (0, import_react2.useCallback)(
2591
+ const { resetViewport: resetViewport2, zoomIn, zoomOut, panToObject, zoomToFit } = useViewportActions(canvasRef, viewportRef, setZoom);
2592
+ const setBackground = (0, import_react3.useCallback)(
2361
2593
  async (url, bgOpts) => {
2362
2594
  const canvas = canvasRef.current;
2363
2595
  if (!canvas) throw new Error("Canvas not ready");
2364
- const resizeOpts = options?.backgroundResize !== false ? typeof options?.backgroundResize === "object" ? { ...options.backgroundResize, ...bgOpts } : { ...bgOpts } : bgOpts?.preserveContrast ? { preserveContrast: true } : void 0;
2596
+ const opts = optionsRef.current;
2597
+ const resizeOpts = opts?.backgroundResize !== false ? typeof opts?.backgroundResize === "object" ? { ...opts.backgroundResize, ...bgOpts } : { ...bgOpts } : bgOpts?.preserveContrast ? { preserveContrast: true } : void 0;
2365
2598
  const img = await setBackgroundImage(canvas, url, resizeOpts);
2366
- if (options?.autoFitToBackground !== false) {
2599
+ if (opts?.autoFitToBackground !== false) {
2367
2600
  fitViewportToBackground(canvas);
2368
2601
  syncZoom(canvasRef, setZoom);
2369
2602
  }
2370
2603
  return img;
2371
2604
  },
2372
- // eslint-disable-next-line react-hooks/exhaustive-deps
2373
2605
  []
2374
2606
  );
2375
2607
  return {
@@ -2394,7 +2626,9 @@ function useEditCanvas(options) {
2394
2626
  /** Zoom out from the canvas center. Default step: 0.2. */
2395
2627
  zoomOut,
2396
2628
  /** Pan the viewport to center on a specific object. */
2397
- panToObject
2629
+ panToObject,
2630
+ /** Zoom and pan to fit a specific object in the viewport. */
2631
+ zoomToFit
2398
2632
  },
2399
2633
  /** Whether vertex edit mode is currently active (reactive). */
2400
2634
  isEditingVertices,
@@ -2426,38 +2660,57 @@ function useEditCanvas(options) {
2426
2660
  /** Whether the canvas has been modified since the last `resetDirty()` call. Requires `trackChanges: true`. */
2427
2661
  isDirty,
2428
2662
  /** Reset the dirty flag (e.g., after a successful save). */
2429
- resetDirty: (0, import_react2.useCallback)(() => setIsDirty(false), [])
2663
+ resetDirty: (0, import_react3.useCallback)(() => setIsDirty(false), []),
2664
+ /** Undo the last change. Requires `history: true`. */
2665
+ undo: (0, import_react3.useCallback)(async () => {
2666
+ const h = historyRef.current;
2667
+ if (!h) return;
2668
+ await h.undo();
2669
+ setCanUndo(h.canUndo());
2670
+ setCanRedo(h.canRedo());
2671
+ }, []),
2672
+ /** Redo a previously undone change. Requires `history: true`. */
2673
+ redo: (0, import_react3.useCallback)(async () => {
2674
+ const h = historyRef.current;
2675
+ if (!h) return;
2676
+ await h.redo();
2677
+ setCanUndo(h.canUndo());
2678
+ setCanRedo(h.canRedo());
2679
+ }, []),
2680
+ /** Whether an undo operation is available (reactive). Requires `history: true`. */
2681
+ canUndo,
2682
+ /** Whether a redo operation is available (reactive). Requires `history: true`. */
2683
+ canRedo
2430
2684
  };
2431
2685
  }
2432
2686
 
2433
2687
  // src/hooks/useViewCanvas.ts
2434
- var import_react3 = require("react");
2435
- var import_fabric18 = require("fabric");
2436
- var VIEW_BORDER_RADIUS = 4;
2688
+ var import_react4 = require("react");
2437
2689
  function lockCanvas(canvas) {
2438
2690
  canvas.selection = false;
2439
2691
  canvas.forEachObject((obj) => {
2440
2692
  obj.selectable = false;
2441
- obj.evented = false;
2442
- if (obj instanceof import_fabric18.Rect && obj.shapeType !== "circle" && obj.data?.type !== "DEVICE") {
2443
- const rx = VIEW_BORDER_RADIUS / (obj.scaleX ?? 1);
2444
- const ry = VIEW_BORDER_RADIUS / (obj.scaleY ?? 1);
2445
- obj.set({ rx, ry });
2446
- }
2447
2693
  });
2448
2694
  }
2449
2695
  function useViewCanvas(options) {
2450
- const canvasRef = (0, import_react3.useRef)(null);
2451
- const viewportRef = (0, import_react3.useRef)(null);
2452
- const [zoom, setZoom] = (0, import_react3.useState)(1);
2453
- const onReady = (0, import_react3.useCallback)(
2696
+ const canvasRef = (0, import_react4.useRef)(null);
2697
+ const viewportRef = (0, import_react4.useRef)(null);
2698
+ const optionsRef = (0, import_react4.useRef)(options);
2699
+ optionsRef.current = options;
2700
+ const [zoom, setZoom] = (0, import_react4.useState)(1);
2701
+ const onReady = (0, import_react4.useCallback)(
2454
2702
  (canvas) => {
2455
2703
  canvasRef.current = canvas;
2456
- if (options?.scaledStrokes !== false) {
2704
+ const opts = optionsRef.current;
2705
+ if (opts?.scaledStrokes !== false) {
2457
2706
  enableScaledStrokes(canvas);
2458
2707
  }
2459
- if (options?.panAndZoom !== false) {
2460
- const panAndZoomOpts = typeof options?.panAndZoom === "object" ? options.panAndZoom : {};
2708
+ if (opts?.borderRadius !== false) {
2709
+ const borderRadiusOpts = typeof opts?.borderRadius === "number" ? { radius: opts.borderRadius } : void 0;
2710
+ enableScaledBorderRadius(canvas, borderRadiusOpts);
2711
+ }
2712
+ if (opts?.panAndZoom !== false) {
2713
+ const panAndZoomOpts = typeof opts?.panAndZoom === "object" ? opts.panAndZoom : {};
2461
2714
  viewportRef.current = enablePanAndZoom(canvas, {
2462
2715
  ...panAndZoomOpts,
2463
2716
  initialMode: "pan"
@@ -2470,8 +2723,8 @@ function useViewCanvas(options) {
2470
2723
  canvas.on("mouse:wheel", () => {
2471
2724
  setZoom(canvas.getZoom());
2472
2725
  });
2473
- const onReadyResult = options?.onReady?.(canvas);
2474
- if (options?.autoFitToBackground !== false) {
2726
+ const onReadyResult = opts?.onReady?.(canvas);
2727
+ if (opts?.autoFitToBackground !== false) {
2475
2728
  Promise.resolve(onReadyResult).then(() => {
2476
2729
  if (canvas.backgroundImage) {
2477
2730
  fitViewportToBackground(canvas);
@@ -2481,26 +2734,21 @@ function useViewCanvas(options) {
2481
2734
  }
2482
2735
  },
2483
2736
  // onReady and panAndZoom are intentionally excluded — we only initialize once
2484
- // eslint-disable-next-line react-hooks/exhaustive-deps
2485
2737
  []
2486
2738
  );
2487
- const { resetViewport: resetViewport2, zoomIn, zoomOut, panToObject } = createViewportActions(
2488
- canvasRef,
2489
- viewportRef,
2490
- setZoom
2491
- );
2739
+ const { resetViewport: resetViewport2, zoomIn, zoomOut, panToObject, zoomToFit } = useViewportActions(canvasRef, viewportRef, setZoom);
2492
2740
  const findObject = (id) => {
2493
2741
  const c = canvasRef.current;
2494
2742
  if (!c) return void 0;
2495
2743
  return c.getObjects().find((o) => o.data?.id === id);
2496
2744
  };
2497
- const setObjectStyle = (0, import_react3.useCallback)((id, style) => {
2745
+ const setObjectStyle = (0, import_react4.useCallback)((id, style) => {
2498
2746
  const obj = findObject(id);
2499
2747
  if (!obj) return;
2500
2748
  obj.set(style);
2501
2749
  canvasRef.current.requestRenderAll();
2502
2750
  }, []);
2503
- const setObjectStyles = (0, import_react3.useCallback)(
2751
+ const setObjectStyles = (0, import_react4.useCallback)(
2504
2752
  (styles) => {
2505
2753
  const c = canvasRef.current;
2506
2754
  if (!c) return;
@@ -2521,7 +2769,7 @@ function useViewCanvas(options) {
2521
2769
  },
2522
2770
  []
2523
2771
  );
2524
- const setObjectStyleByType = (0, import_react3.useCallback)(
2772
+ const setObjectStyleByType = (0, import_react4.useCallback)(
2525
2773
  (type, style) => {
2526
2774
  const c = canvasRef.current;
2527
2775
  if (!c) return;
@@ -2552,7 +2800,9 @@ function useViewCanvas(options) {
2552
2800
  /** Zoom out from the canvas center. Default step: 0.2. */
2553
2801
  zoomOut,
2554
2802
  /** Pan the viewport to center on a specific object. */
2555
- panToObject
2803
+ panToObject,
2804
+ /** Zoom and pan to fit a specific object in the viewport. */
2805
+ zoomToFit
2556
2806
  },
2557
2807
  /** Update a single object's visual style by its `data.id`. */
2558
2808
  setObjectStyle,
@@ -2564,11 +2814,11 @@ function useViewCanvas(options) {
2564
2814
  }
2565
2815
 
2566
2816
  // src/hooks/useCanvasEvents.ts
2567
- var import_react4 = require("react");
2817
+ var import_react5 = require("react");
2568
2818
  function useCanvasEvents(canvasRef, events) {
2569
- const eventsRef = (0, import_react4.useRef)(events);
2819
+ const eventsRef = (0, import_react5.useRef)(events);
2570
2820
  eventsRef.current = events;
2571
- (0, import_react4.useEffect)(() => {
2821
+ (0, import_react5.useEffect)(() => {
2572
2822
  const canvas = canvasRef.current;
2573
2823
  if (!canvas) return;
2574
2824
  const wrappers = /* @__PURE__ */ new Map();
@@ -2589,17 +2839,17 @@ function useCanvasEvents(canvasRef, events) {
2589
2839
  }
2590
2840
 
2591
2841
  // src/hooks/useCanvasTooltip.ts
2592
- var import_react5 = require("react");
2842
+ var import_react6 = require("react");
2593
2843
  function useCanvasTooltip(canvasRef, options) {
2594
- const [state, setState] = (0, import_react5.useState)({
2844
+ const [state, setState] = (0, import_react6.useState)({
2595
2845
  visible: false,
2596
2846
  content: null,
2597
2847
  position: { x: 0, y: 0 }
2598
2848
  });
2599
- const hoveredObjectRef = (0, import_react5.useRef)(null);
2600
- const optionsRef = (0, import_react5.useRef)(options);
2849
+ const hoveredObjectRef = (0, import_react6.useRef)(null);
2850
+ const optionsRef = (0, import_react6.useRef)(options);
2601
2851
  optionsRef.current = options;
2602
- (0, import_react5.useEffect)(() => {
2852
+ (0, import_react6.useEffect)(() => {
2603
2853
  const canvas = canvasRef.current;
2604
2854
  if (!canvas) return;
2605
2855
  function calculatePosition(target) {
@@ -2653,13 +2903,13 @@ function useCanvasTooltip(canvasRef, options) {
2653
2903
  }
2654
2904
 
2655
2905
  // src/hooks/useCanvasClick.ts
2656
- var import_react6 = require("react");
2906
+ var import_react7 = require("react");
2657
2907
  function useCanvasClick(canvasRef, onClick, options) {
2658
- const onClickRef = (0, import_react6.useRef)(onClick);
2908
+ const onClickRef = (0, import_react7.useRef)(onClick);
2659
2909
  onClickRef.current = onClick;
2660
- const optionsRef = (0, import_react6.useRef)(options);
2910
+ const optionsRef = (0, import_react7.useRef)(options);
2661
2911
  optionsRef.current = options;
2662
- (0, import_react6.useEffect)(() => {
2912
+ (0, import_react7.useEffect)(() => {
2663
2913
  const canvas = canvasRef.current;
2664
2914
  if (!canvas) return;
2665
2915
  let mouseDown = null;
@@ -2704,13 +2954,13 @@ function useCanvasClick(canvasRef, onClick, options) {
2704
2954
  }
2705
2955
 
2706
2956
  // src/hooks/useObjectOverlay.ts
2707
- var import_react7 = require("react");
2708
- var import_fabric19 = require("fabric");
2957
+ var import_react8 = require("react");
2958
+ var import_fabric18 = require("fabric");
2709
2959
  function useObjectOverlay(canvasRef, object, options) {
2710
- const containerRef = (0, import_react7.useRef)(null);
2711
- const optionsRef = (0, import_react7.useRef)(options);
2960
+ const containerRef = (0, import_react8.useRef)(null);
2961
+ const optionsRef = (0, import_react8.useRef)(options);
2712
2962
  optionsRef.current = options;
2713
- (0, import_react7.useEffect)(() => {
2963
+ (0, import_react8.useEffect)(() => {
2714
2964
  const canvas = canvasRef.current;
2715
2965
  if (!canvas || !object) return;
2716
2966
  function update() {
@@ -2722,39 +2972,44 @@ function useObjectOverlay(canvasRef, object, options) {
2722
2972
  const center = object.getCenterPoint();
2723
2973
  const actualWidth = (object.width ?? 0) * (object.scaleX ?? 1);
2724
2974
  const actualHeight = (object.height ?? 0) * (object.scaleY ?? 1);
2725
- const screenCoords = import_fabric19.util.transformPoint(center, vt);
2975
+ const screenCoords = import_fabric18.util.transformPoint(center, vt);
2726
2976
  const screenWidth = actualWidth * zoom;
2727
2977
  const screenHeight = actualHeight * zoom;
2728
- el.style.left = `${screenCoords.x - screenWidth / 2}px`;
2729
- el.style.top = `${screenCoords.y - screenHeight / 2}px`;
2730
- el.style.width = `${screenWidth}px`;
2731
- el.style.height = `${screenHeight}px`;
2732
2978
  const angle = object.angle ?? 0;
2733
- el.style.rotate = angle !== 0 ? `${angle}deg` : "";
2734
2979
  const opts = optionsRef.current;
2735
- if (opts?.autoScaleContent) {
2736
- const contentScale = Math.min(screenWidth, screenHeight) / 100;
2737
- const clampedScale = Math.max(0.1, Math.min(contentScale, 2));
2738
- el.style.setProperty("--overlay-scale", String(clampedScale));
2739
- if (opts.textSelector) {
2740
- const textMinScale = opts.textMinScale ?? 0.5;
2980
+ if (opts?.autoScaleContent !== false) {
2981
+ el.style.left = `${screenCoords.x - actualWidth / 2}px`;
2982
+ el.style.top = `${screenCoords.y - actualHeight / 2}px`;
2983
+ el.style.width = `${actualWidth}px`;
2984
+ el.style.height = `${actualHeight}px`;
2985
+ el.style.transformOrigin = "center center";
2986
+ el.style.transform = angle !== 0 ? `scale(${zoom}) rotate(${angle}deg)` : `scale(${zoom})`;
2987
+ el.style.rotate = "";
2988
+ el.style.setProperty("--overlay-scale", String(zoom));
2989
+ if (opts?.textSelector) {
2990
+ const textMinScale = opts?.textMinScale ?? 0.5;
2741
2991
  const textEls = el.querySelectorAll(opts.textSelector);
2742
- const display = clampedScale < textMinScale ? "none" : "";
2992
+ const display = zoom < textMinScale ? "none" : "";
2743
2993
  textEls.forEach((t) => {
2744
2994
  t.style.display = display;
2745
2995
  });
2746
2996
  }
2997
+ } else {
2998
+ el.style.left = `${screenCoords.x - screenWidth / 2}px`;
2999
+ el.style.top = `${screenCoords.y - screenHeight / 2}px`;
3000
+ el.style.width = `${screenWidth}px`;
3001
+ el.style.height = `${screenHeight}px`;
3002
+ el.style.transform = "";
3003
+ el.style.rotate = angle !== 0 ? `${angle}deg` : "";
2747
3004
  }
2748
3005
  }
2749
3006
  update();
2750
3007
  canvas.on("after:render", update);
2751
- canvas.on("mouse:wheel", update);
2752
3008
  object.on("moving", update);
2753
3009
  object.on("scaling", update);
2754
3010
  object.on("rotating", update);
2755
3011
  return () => {
2756
3012
  canvas.off("after:render", update);
2757
- canvas.off("mouse:wheel", update);
2758
3013
  object.off("moving", update);
2759
3014
  object.off("scaling", update);
2760
3015
  object.off("rotating", update);
@@ -2764,7 +3019,7 @@ function useObjectOverlay(canvasRef, object, options) {
2764
3019
  }
2765
3020
 
2766
3021
  // src/index.ts
2767
- var import_fabric20 = require("fabric");
3022
+ var import_fabric19 = require("fabric");
2768
3023
  // Annotate the CommonJS export names for ESM import in node:
2769
3024
  0 && (module.exports = {
2770
3025
  Canvas,
@@ -2782,6 +3037,7 @@ var import_fabric20 = require("fabric");
2782
3037
  Rect,
2783
3038
  createCircle,
2784
3039
  createCircleAtPoint,
3040
+ createHistoryTracker,
2785
3041
  createPolygon,
2786
3042
  createPolygonAtPoint,
2787
3043
  createPolygonFromDrag,
@@ -2799,6 +3055,7 @@ var import_fabric20 = require("fabric");
2799
3055
  enableObjectAlignment,
2800
3056
  enablePanAndZoom,
2801
3057
  enableRotationSnap,
3058
+ enableScaledBorderRadius,
2802
3059
  enableScaledStrokes,
2803
3060
  enableVertexEdit,
2804
3061
  fitViewportToBackground,