@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.
- package/dist/Canvas/Canvas.d.ts +12 -1
- package/dist/Canvas/Canvas.d.ts.map +1 -1
- package/dist/background.d.ts.map +1 -1
- package/dist/constants.d.ts +2 -2
- package/dist/constants.d.ts.map +1 -1
- package/dist/fabricAugmentation.d.ts +3 -1
- package/dist/fabricAugmentation.d.ts.map +1 -1
- package/dist/history.d.ts +32 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/hooks/shared.d.ts +6 -4
- package/dist/hooks/shared.d.ts.map +1 -1
- package/dist/hooks/useEditCanvas.d.ts +22 -0
- package/dist/hooks/useEditCanvas.d.ts.map +1 -1
- package/dist/hooks/useObjectOverlay.d.ts +4 -3
- package/dist/hooks/useObjectOverlay.d.ts.map +1 -1
- package/dist/hooks/useViewCanvas.d.ts +7 -0
- package/dist/hooks/useViewCanvas.d.ts.map +1 -1
- package/dist/index.cjs +416 -159
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +370 -112
- package/dist/index.js.map +1 -1
- package/dist/interactions/dragToCreate.d.ts +6 -1
- package/dist/interactions/dragToCreate.d.ts.map +1 -1
- package/dist/interactions/drawToCreate.d.ts +8 -3
- package/dist/interactions/drawToCreate.d.ts.map +1 -1
- package/dist/interactions/vertexEdit.d.ts +5 -1
- package/dist/interactions/vertexEdit.d.ts.map +1 -1
- package/dist/serialization.d.ts +25 -2
- package/dist/serialization.d.ts.map +1 -1
- package/dist/shapes/polygon.d.ts +2 -2
- package/dist/shapes/polygon.d.ts.map +1 -1
- package/dist/styles.d.ts +6 -0
- package/dist/styles.d.ts.map +1 -1
- package/dist/types.d.ts +8 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/viewport.d.ts +12 -2
- package/dist/viewport.d.ts.map +1 -1
- 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: () =>
|
|
31
|
-
FabricImage: () =>
|
|
32
|
-
FabricObject: () =>
|
|
33
|
-
Point: () =>
|
|
34
|
-
Polygon: () =>
|
|
35
|
-
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: () =>
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
582
|
-
|
|
583
|
-
const
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
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,
|
|
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, ...
|
|
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,
|
|
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, ...
|
|
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
|
|
1855
|
+
const obj = options?.factory ? options.factory(canvas, [...points]) : createPolygonFromVertices(canvas, points, options?.style);
|
|
1803
1856
|
if (options?.data) {
|
|
1804
|
-
|
|
1857
|
+
obj.data = options.data;
|
|
1805
1858
|
}
|
|
1806
1859
|
canvas.selection = previousSelection;
|
|
1807
1860
|
canvas.requestRenderAll();
|
|
1808
1861
|
restoreViewport(options?.viewport);
|
|
1809
|
-
options?.onCreated?.(
|
|
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,
|
|
2215
|
-
const viewportRef = (0,
|
|
2216
|
-
const alignmentCleanupRef = (0,
|
|
2217
|
-
const rotationSnapCleanupRef = (0,
|
|
2218
|
-
const modeCleanupRef = (0,
|
|
2219
|
-
const vertexEditCleanupRef = (0,
|
|
2220
|
-
const keyboardCleanupRef = (0,
|
|
2221
|
-
const
|
|
2222
|
-
const
|
|
2223
|
-
|
|
2224
|
-
const
|
|
2225
|
-
|
|
2226
|
-
|
|
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
|
-
|
|
2238
|
-
obj.
|
|
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,
|
|
2464
|
+
const onReady = (0, import_react3.useCallback)(
|
|
2253
2465
|
(canvas) => {
|
|
2254
2466
|
canvasRef.current = canvas;
|
|
2255
|
-
|
|
2467
|
+
const opts = optionsRef.current;
|
|
2468
|
+
if (opts?.scaledStrokes !== false) {
|
|
2256
2469
|
enableScaledStrokes(canvas);
|
|
2257
2470
|
}
|
|
2258
|
-
if (
|
|
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,
|
|
2262
|
-
if (
|
|
2478
|
+
setCanvasAlignmentEnabled(canvas, opts?.enableAlignment);
|
|
2479
|
+
if (opts?.panAndZoom !== false) {
|
|
2263
2480
|
viewportRef.current = enablePanAndZoom(
|
|
2264
2481
|
canvas,
|
|
2265
|
-
typeof
|
|
2482
|
+
typeof opts?.panAndZoom === "object" ? opts.panAndZoom : void 0
|
|
2266
2483
|
);
|
|
2267
2484
|
}
|
|
2268
2485
|
const alignmentEnabled = resolveAlignmentEnabled(
|
|
2269
|
-
|
|
2270
|
-
|
|
2486
|
+
opts?.enableAlignment,
|
|
2487
|
+
opts?.alignment
|
|
2271
2488
|
);
|
|
2272
2489
|
if (alignmentEnabled) {
|
|
2273
2490
|
alignmentCleanupRef.current = enableObjectAlignment(
|
|
2274
2491
|
canvas,
|
|
2275
|
-
typeof
|
|
2492
|
+
typeof opts?.alignment === "object" ? opts.alignment : void 0
|
|
2276
2493
|
);
|
|
2277
2494
|
}
|
|
2278
|
-
if (
|
|
2495
|
+
if (opts?.rotationSnap !== false) {
|
|
2279
2496
|
rotationSnapCleanupRef.current = enableRotationSnap(
|
|
2280
2497
|
canvas,
|
|
2281
|
-
typeof
|
|
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 (
|
|
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 (
|
|
2302
|
-
const
|
|
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
|
-
|
|
2308
|
-
|
|
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
|
-
|
|
2320
|
-
|
|
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,
|
|
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,
|
|
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 } =
|
|
2356
|
-
|
|
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
|
|
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 (
|
|
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,
|
|
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
|
|
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,
|
|
2451
|
-
const viewportRef = (0,
|
|
2452
|
-
const
|
|
2453
|
-
|
|
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
|
-
|
|
2704
|
+
const opts = optionsRef.current;
|
|
2705
|
+
if (opts?.scaledStrokes !== false) {
|
|
2457
2706
|
enableScaledStrokes(canvas);
|
|
2458
2707
|
}
|
|
2459
|
-
if (
|
|
2460
|
-
const
|
|
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 =
|
|
2474
|
-
if (
|
|
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 } =
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
2817
|
+
var import_react5 = require("react");
|
|
2568
2818
|
function useCanvasEvents(canvasRef, events) {
|
|
2569
|
-
const eventsRef = (0,
|
|
2819
|
+
const eventsRef = (0, import_react5.useRef)(events);
|
|
2570
2820
|
eventsRef.current = events;
|
|
2571
|
-
(0,
|
|
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
|
|
2842
|
+
var import_react6 = require("react");
|
|
2593
2843
|
function useCanvasTooltip(canvasRef, options) {
|
|
2594
|
-
const [state, setState] = (0,
|
|
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,
|
|
2600
|
-
const optionsRef = (0,
|
|
2849
|
+
const hoveredObjectRef = (0, import_react6.useRef)(null);
|
|
2850
|
+
const optionsRef = (0, import_react6.useRef)(options);
|
|
2601
2851
|
optionsRef.current = options;
|
|
2602
|
-
(0,
|
|
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
|
|
2906
|
+
var import_react7 = require("react");
|
|
2657
2907
|
function useCanvasClick(canvasRef, onClick, options) {
|
|
2658
|
-
const onClickRef = (0,
|
|
2908
|
+
const onClickRef = (0, import_react7.useRef)(onClick);
|
|
2659
2909
|
onClickRef.current = onClick;
|
|
2660
|
-
const optionsRef = (0,
|
|
2910
|
+
const optionsRef = (0, import_react7.useRef)(options);
|
|
2661
2911
|
optionsRef.current = options;
|
|
2662
|
-
(0,
|
|
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
|
|
2708
|
-
var
|
|
2957
|
+
var import_react8 = require("react");
|
|
2958
|
+
var import_fabric18 = require("fabric");
|
|
2709
2959
|
function useObjectOverlay(canvasRef, object, options) {
|
|
2710
|
-
const containerRef = (0,
|
|
2711
|
-
const optionsRef = (0,
|
|
2960
|
+
const containerRef = (0, import_react8.useRef)(null);
|
|
2961
|
+
const optionsRef = (0, import_react8.useRef)(options);
|
|
2712
2962
|
optionsRef.current = options;
|
|
2713
|
-
(0,
|
|
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 =
|
|
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
|
-
|
|
2737
|
-
|
|
2738
|
-
el.style.
|
|
2739
|
-
|
|
2740
|
-
|
|
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 =
|
|
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
|
|
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,
|