@bwp-web/canvas 0.5.1 → 0.6.1
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 +411 -150
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +374 -111
- package/dist/index.js.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 +20 -8
- 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/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
|
@@ -35,6 +35,7 @@ __export(index_exports, {
|
|
|
35
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,
|
|
@@ -115,7 +116,9 @@ function Canvas({
|
|
|
115
116
|
height,
|
|
116
117
|
className,
|
|
117
118
|
style,
|
|
118
|
-
onReady
|
|
119
|
+
onReady,
|
|
120
|
+
keyboardShortcuts,
|
|
121
|
+
fabricOptions
|
|
119
122
|
}) {
|
|
120
123
|
const canvasRef = (0, import_react.useRef)(null);
|
|
121
124
|
const wrapperRef = (0, import_react.useRef)(null);
|
|
@@ -127,11 +130,12 @@ function Canvas({
|
|
|
127
130
|
const initialWidth = isFixedSize ? width : wrapper.clientWidth || 800;
|
|
128
131
|
const initialHeight = isFixedSize ? height : wrapper.clientHeight || 600;
|
|
129
132
|
const fabricCanvas = new import_fabric2.Canvas(el, {
|
|
133
|
+
...fabricOptions,
|
|
130
134
|
width: initialWidth,
|
|
131
135
|
height: initialHeight
|
|
132
136
|
});
|
|
133
137
|
onReady?.(fabricCanvas);
|
|
134
|
-
const cleanupShortcuts = enableKeyboardShortcuts(fabricCanvas);
|
|
138
|
+
const cleanupShortcuts = keyboardShortcuts ? enableKeyboardShortcuts(fabricCanvas) : void 0;
|
|
135
139
|
let observer;
|
|
136
140
|
let rafId = 0;
|
|
137
141
|
if (!isFixedSize) {
|
|
@@ -155,7 +159,7 @@ function Canvas({
|
|
|
155
159
|
return () => {
|
|
156
160
|
cancelAnimationFrame(rafId);
|
|
157
161
|
observer?.disconnect();
|
|
158
|
-
cleanupShortcuts();
|
|
162
|
+
cleanupShortcuts?.();
|
|
159
163
|
fabricCanvas.dispose();
|
|
160
164
|
};
|
|
161
165
|
}, []);
|
|
@@ -164,7 +168,7 @@ function Canvas({
|
|
|
164
168
|
}
|
|
165
169
|
|
|
166
170
|
// src/hooks/useEditCanvas.ts
|
|
167
|
-
var
|
|
171
|
+
var import_react3 = require("react");
|
|
168
172
|
var import_fabric17 = require("fabric");
|
|
169
173
|
|
|
170
174
|
// src/viewport.ts
|
|
@@ -173,7 +177,7 @@ var import_fabric3 = require("fabric");
|
|
|
173
177
|
// src/constants.ts
|
|
174
178
|
var DEFAULT_MIN_ZOOM = 0.2;
|
|
175
179
|
var DEFAULT_MAX_ZOOM = 10;
|
|
176
|
-
var DEFAULT_ZOOM_FACTOR =
|
|
180
|
+
var DEFAULT_ZOOM_FACTOR = 0.999;
|
|
177
181
|
var DEFAULT_ZOOM_STEP = 1.2;
|
|
178
182
|
var DEFAULT_VIEWPORT_PADDING = 0.05;
|
|
179
183
|
var BASE_CANVAS_SIZE = 1e3;
|
|
@@ -216,7 +220,7 @@ function setupWheelZoom(canvas, bounds, zoomFactor, isEnabled) {
|
|
|
216
220
|
e.stopPropagation();
|
|
217
221
|
const delta = e.deltaY;
|
|
218
222
|
let zoom = canvas.getZoom();
|
|
219
|
-
zoom
|
|
223
|
+
zoom *= zoomFactor ** delta;
|
|
220
224
|
zoom = Math.min(Math.max(zoom, bounds.minZoom), bounds.maxZoom);
|
|
221
225
|
canvas.zoomToPoint(new import_fabric3.Point(e.offsetX, e.offsetY), zoom);
|
|
222
226
|
};
|
|
@@ -324,8 +328,15 @@ function enablePanAndZoom(canvas, options) {
|
|
|
324
328
|
const zoomFactor = options?.zoomFactor ?? DEFAULT_ZOOM_FACTOR;
|
|
325
329
|
let mode = options?.initialMode ?? "select";
|
|
326
330
|
let enabled = true;
|
|
331
|
+
let currentAnimRafId = null;
|
|
327
332
|
const isEnabled = () => enabled;
|
|
328
333
|
const getMode = () => mode;
|
|
334
|
+
function cancelAnimation() {
|
|
335
|
+
if (currentAnimRafId !== null) {
|
|
336
|
+
cancelAnimationFrame(currentAnimRafId);
|
|
337
|
+
currentAnimRafId = null;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
329
340
|
const handleWheel = setupWheelZoom(canvas, bounds, zoomFactor, isEnabled);
|
|
330
341
|
const panHandlers = setupMousePan(canvas, getMode, isEnabled);
|
|
331
342
|
const cleanupPinch = setupPinchZoom(canvas, bounds, isEnabled);
|
|
@@ -371,6 +382,7 @@ function enablePanAndZoom(canvas, options) {
|
|
|
371
382
|
);
|
|
372
383
|
},
|
|
373
384
|
panToObject(object, panOpts) {
|
|
385
|
+
cancelAnimation();
|
|
374
386
|
const zoom = canvas.getZoom();
|
|
375
387
|
const objectCenter = object.getCenterPoint();
|
|
376
388
|
const canvasCenterX = canvas.getWidth() / 2;
|
|
@@ -413,12 +425,37 @@ function enablePanAndZoom(canvas, options) {
|
|
|
413
425
|
currentY
|
|
414
426
|
]);
|
|
415
427
|
if (t < 1) {
|
|
416
|
-
requestAnimationFrame(step);
|
|
428
|
+
currentAnimRafId = requestAnimationFrame(step);
|
|
429
|
+
} else {
|
|
430
|
+
currentAnimRafId = null;
|
|
417
431
|
}
|
|
418
432
|
}
|
|
419
|
-
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]);
|
|
420
456
|
},
|
|
421
457
|
cleanup() {
|
|
458
|
+
cancelAnimation();
|
|
422
459
|
canvas.off("mouse:wheel", handleWheel);
|
|
423
460
|
canvas.off("mouse:down", panHandlers.handleMouseDown);
|
|
424
461
|
canvas.off("mouse:move", panHandlers.handleMouseMove);
|
|
@@ -431,6 +468,9 @@ function resetViewport(canvas) {
|
|
|
431
468
|
canvas.setViewportTransform([1, 0, 0, 1, 0, 0]);
|
|
432
469
|
}
|
|
433
470
|
|
|
471
|
+
// src/hooks/shared.ts
|
|
472
|
+
var import_react2 = require("react");
|
|
473
|
+
|
|
434
474
|
// src/background.ts
|
|
435
475
|
var import_fabric4 = require("fabric");
|
|
436
476
|
function getBackgroundImage(canvas) {
|
|
@@ -561,7 +601,8 @@ function resizeImageUrl(url, options) {
|
|
|
561
601
|
async function setBackgroundImage(canvas, url, options) {
|
|
562
602
|
const prevContrast = options?.preserveContrast ? getBackgroundContrast(canvas) : void 0;
|
|
563
603
|
let imageUrl = url;
|
|
564
|
-
|
|
604
|
+
const hasResizeOptions = options?.maxSize !== void 0 || options?.minSize !== void 0;
|
|
605
|
+
if (hasResizeOptions) {
|
|
565
606
|
const result = await resizeImageUrl(url, options);
|
|
566
607
|
imageUrl = result.url;
|
|
567
608
|
}
|
|
@@ -579,29 +620,35 @@ function syncZoom(canvasRef, setZoom) {
|
|
|
579
620
|
const canvas = canvasRef.current;
|
|
580
621
|
if (canvas) setZoom(canvas.getZoom());
|
|
581
622
|
}
|
|
582
|
-
function
|
|
583
|
-
|
|
584
|
-
const
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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
|
+
}, []);
|
|
605
652
|
}
|
|
606
653
|
function resolveAlignmentEnabled(enableAlignment, alignmentProp) {
|
|
607
654
|
if (enableAlignment !== void 0) return enableAlignment;
|
|
@@ -1660,7 +1707,7 @@ function createPolygonAtPoint(canvas, point, options) {
|
|
|
1660
1707
|
canvas.requestRenderAll();
|
|
1661
1708
|
return polygon;
|
|
1662
1709
|
}
|
|
1663
|
-
function createPolygonFromDrag(canvas, start, end,
|
|
1710
|
+
function createPolygonFromDrag(canvas, start, end, options) {
|
|
1664
1711
|
const width = Math.abs(end.x - start.x);
|
|
1665
1712
|
const height = Math.abs(end.y - start.y);
|
|
1666
1713
|
const left = Math.min(start.x, end.x) + width / 2;
|
|
@@ -1672,16 +1719,16 @@ function createPolygonFromDrag(canvas, start, end, style) {
|
|
|
1672
1719
|
{ x: width, y: height },
|
|
1673
1720
|
{ x: 0, y: height }
|
|
1674
1721
|
],
|
|
1675
|
-
{ ...DEFAULT_SHAPE_STYLE, left, top, ...
|
|
1722
|
+
{ ...DEFAULT_SHAPE_STYLE, left, top, ...options }
|
|
1676
1723
|
);
|
|
1677
1724
|
canvas.add(polygon);
|
|
1678
1725
|
canvas.requestRenderAll();
|
|
1679
1726
|
return polygon;
|
|
1680
1727
|
}
|
|
1681
|
-
function createPolygonFromVertices(canvas, points,
|
|
1728
|
+
function createPolygonFromVertices(canvas, points, options) {
|
|
1682
1729
|
const polygon = new import_fabric13.Polygon(
|
|
1683
1730
|
points.map((p) => ({ x: p.x, y: p.y })),
|
|
1684
|
-
{ ...DEFAULT_SHAPE_STYLE, ...
|
|
1731
|
+
{ ...DEFAULT_SHAPE_STYLE, ...options }
|
|
1685
1732
|
);
|
|
1686
1733
|
canvas.add(polygon);
|
|
1687
1734
|
canvas.requestRenderAll();
|
|
@@ -1805,14 +1852,14 @@ function enableDrawToCreate(canvas, options) {
|
|
|
1805
1852
|
const finalize = () => {
|
|
1806
1853
|
removePreviewElements();
|
|
1807
1854
|
snapping.clearSnapResult();
|
|
1808
|
-
const
|
|
1855
|
+
const obj = options?.factory ? options.factory(canvas, [...points]) : createPolygonFromVertices(canvas, points, options?.style);
|
|
1809
1856
|
if (options?.data) {
|
|
1810
|
-
|
|
1857
|
+
obj.data = options.data;
|
|
1811
1858
|
}
|
|
1812
1859
|
canvas.selection = previousSelection;
|
|
1813
1860
|
canvas.requestRenderAll();
|
|
1814
1861
|
restoreViewport(options?.viewport);
|
|
1815
|
-
options?.onCreated?.(
|
|
1862
|
+
options?.onCreated?.(obj);
|
|
1816
1863
|
points.length = 0;
|
|
1817
1864
|
};
|
|
1818
1865
|
const handleMouseDown = (event) => {
|
|
@@ -2121,7 +2168,7 @@ function enableVertexEdit(canvas, polygon, options, onExit) {
|
|
|
2121
2168
|
});
|
|
2122
2169
|
canvas.discardActiveObject();
|
|
2123
2170
|
canvas.requestRenderAll();
|
|
2124
|
-
onExit?.();
|
|
2171
|
+
(options?.onExit ?? onExit)?.();
|
|
2125
2172
|
}
|
|
2126
2173
|
return cleanup;
|
|
2127
2174
|
}
|
|
@@ -2130,7 +2177,7 @@ function enableVertexEdit(canvas, polygon, options, onExit) {
|
|
|
2130
2177
|
var import_fabric16 = require("fabric");
|
|
2131
2178
|
var strokeBaseMap = /* @__PURE__ */ new WeakMap();
|
|
2132
2179
|
var borderRadiusBaseMap = /* @__PURE__ */ new WeakMap();
|
|
2133
|
-
var
|
|
2180
|
+
var DEFAULT_VIEW_BORDER_RADIUS = 4;
|
|
2134
2181
|
function enableScaledStrokes(canvas) {
|
|
2135
2182
|
function applyScaledStrokes() {
|
|
2136
2183
|
const zoom = canvas.getZoom();
|
|
@@ -2155,13 +2202,14 @@ function enableScaledStrokes(canvas) {
|
|
|
2155
2202
|
});
|
|
2156
2203
|
};
|
|
2157
2204
|
}
|
|
2158
|
-
function enableScaledBorderRadius(canvas) {
|
|
2205
|
+
function enableScaledBorderRadius(canvas, options) {
|
|
2206
|
+
const radius = options?.radius ?? DEFAULT_VIEW_BORDER_RADIUS;
|
|
2159
2207
|
function applyScaledBorderRadius() {
|
|
2160
2208
|
canvas.forEachObject((obj) => {
|
|
2161
2209
|
if (!(obj instanceof import_fabric16.Rect)) return;
|
|
2162
2210
|
if (!borderRadiusBaseMap.has(obj)) return;
|
|
2163
|
-
const rx =
|
|
2164
|
-
const ry =
|
|
2211
|
+
const rx = radius / (obj.scaleX ?? 1);
|
|
2212
|
+
const ry = radius / (obj.scaleY ?? 1);
|
|
2165
2213
|
obj.set({ rx, ry });
|
|
2166
2214
|
});
|
|
2167
2215
|
}
|
|
@@ -2211,19 +2259,81 @@ function serializeCanvas(canvas, options) {
|
|
|
2211
2259
|
obj.set({ rx: base.rx, ry: base.ry });
|
|
2212
2260
|
}
|
|
2213
2261
|
});
|
|
2262
|
+
const savedOrigins = /* @__PURE__ */ new Map();
|
|
2263
|
+
canvas.forEachObject((obj) => {
|
|
2264
|
+
if (obj.originX === "left" && obj.originY === "top") return;
|
|
2265
|
+
savedOrigins.set(obj, {
|
|
2266
|
+
originX: obj.originX,
|
|
2267
|
+
originY: obj.originY,
|
|
2268
|
+
left: obj.left ?? 0,
|
|
2269
|
+
top: obj.top ?? 0
|
|
2270
|
+
});
|
|
2271
|
+
const leftTop = obj.getPositionByOrigin("left", "top");
|
|
2272
|
+
obj.set({ originX: "left", originY: "top", left: leftTop.x, top: leftTop.y });
|
|
2273
|
+
});
|
|
2274
|
+
const bg = canvas.backgroundImage;
|
|
2275
|
+
let savedBgOrigin = null;
|
|
2276
|
+
if (bg instanceof import_fabric16.FabricImage && (bg.originX !== "left" || bg.originY !== "top")) {
|
|
2277
|
+
savedBgOrigin = {
|
|
2278
|
+
originX: bg.originX,
|
|
2279
|
+
originY: bg.originY,
|
|
2280
|
+
left: bg.left ?? 0,
|
|
2281
|
+
top: bg.top ?? 0
|
|
2282
|
+
};
|
|
2283
|
+
const leftTop = bg.getPositionByOrigin("left", "top");
|
|
2284
|
+
bg.set({ originX: "left", originY: "top", left: leftTop.x, top: leftTop.y });
|
|
2285
|
+
}
|
|
2286
|
+
const savedData = /* @__PURE__ */ new Map();
|
|
2287
|
+
canvas.forEachObject((obj) => {
|
|
2288
|
+
const base = strokeBaseMap.get(obj) ?? obj.strokeWidth;
|
|
2289
|
+
if (base !== void 0 && base !== 0 && obj.data) {
|
|
2290
|
+
savedData.set(obj, obj.data);
|
|
2291
|
+
obj.data = {
|
|
2292
|
+
...obj.data,
|
|
2293
|
+
strokeWidthBase: base
|
|
2294
|
+
};
|
|
2295
|
+
}
|
|
2296
|
+
});
|
|
2214
2297
|
const json = canvas.toObject(properties);
|
|
2215
2298
|
delete json.backgroundColor;
|
|
2299
|
+
json.backgroundFilters = {
|
|
2300
|
+
opacity: getBackgroundContrast(canvas),
|
|
2301
|
+
inverted: getBackgroundInverted(canvas)
|
|
2302
|
+
};
|
|
2216
2303
|
scaledWidths.forEach((scaled, obj) => {
|
|
2217
2304
|
obj.strokeWidth = scaled;
|
|
2218
2305
|
});
|
|
2219
2306
|
appliedRadii.forEach((radii, obj) => {
|
|
2220
2307
|
obj.set({ rx: radii.rx, ry: radii.ry });
|
|
2221
2308
|
});
|
|
2309
|
+
savedOrigins.forEach((saved, obj) => {
|
|
2310
|
+
obj.set(saved);
|
|
2311
|
+
});
|
|
2312
|
+
if (savedBgOrigin && bg instanceof import_fabric16.FabricImage) {
|
|
2313
|
+
bg.set(savedBgOrigin);
|
|
2314
|
+
}
|
|
2315
|
+
savedData.forEach((originalData, obj) => {
|
|
2316
|
+
obj.data = originalData;
|
|
2317
|
+
});
|
|
2222
2318
|
return json;
|
|
2223
2319
|
}
|
|
2224
2320
|
async function loadCanvas(canvas, json, options) {
|
|
2225
2321
|
await canvas.loadFromJSON(json);
|
|
2226
2322
|
canvas.backgroundColor = "";
|
|
2323
|
+
delete canvas.backgroundFilters;
|
|
2324
|
+
const bg = canvas.backgroundImage;
|
|
2325
|
+
if (bg instanceof import_fabric16.FabricImage) {
|
|
2326
|
+
if (bg.originX !== "center" || bg.originY !== "center") {
|
|
2327
|
+
const center = bg.getCenterPoint();
|
|
2328
|
+
bg.set({
|
|
2329
|
+
originX: "center",
|
|
2330
|
+
originY: "center",
|
|
2331
|
+
left: center.x,
|
|
2332
|
+
top: center.y
|
|
2333
|
+
});
|
|
2334
|
+
bg.setCoords();
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2227
2337
|
if (options?.filter) {
|
|
2228
2338
|
const toRemove = [];
|
|
2229
2339
|
canvas.forEachObject((obj) => {
|
|
@@ -2243,14 +2353,19 @@ async function loadCanvas(canvas, json, options) {
|
|
|
2243
2353
|
obj.setCoords();
|
|
2244
2354
|
});
|
|
2245
2355
|
canvas.forEachObject((obj) => {
|
|
2356
|
+
const data = obj.data;
|
|
2357
|
+
if (data?.strokeWidthBase !== void 0) {
|
|
2358
|
+
delete data.strokeWidthBase;
|
|
2359
|
+
}
|
|
2246
2360
|
obj.set(DEFAULT_CONTROL_STYLE);
|
|
2247
2361
|
if (obj.shapeType === "circle" && obj instanceof import_fabric16.Rect) {
|
|
2248
2362
|
restoreCircleConstraints(obj);
|
|
2249
2363
|
}
|
|
2250
|
-
|
|
2364
|
+
const borderRadius = options?.borderRadius ?? DEFAULT_VIEW_BORDER_RADIUS;
|
|
2365
|
+
if (borderRadius !== false && obj instanceof import_fabric16.Rect && obj.shapeType !== "circle" && obj.data?.type !== "DEVICE") {
|
|
2251
2366
|
borderRadiusBaseMap.set(obj, { rx: obj.rx ?? 0, ry: obj.ry ?? 0 });
|
|
2252
|
-
const rx =
|
|
2253
|
-
const ry =
|
|
2367
|
+
const rx = borderRadius / (obj.scaleX ?? 1);
|
|
2368
|
+
const ry = borderRadius / (obj.scaleY ?? 1);
|
|
2254
2369
|
obj.set({ rx, ry });
|
|
2255
2370
|
}
|
|
2256
2371
|
});
|
|
@@ -2258,21 +2373,110 @@ async function loadCanvas(canvas, json, options) {
|
|
|
2258
2373
|
return canvas.getObjects();
|
|
2259
2374
|
}
|
|
2260
2375
|
|
|
2376
|
+
// src/history.ts
|
|
2377
|
+
function createHistoryTracker(canvas, options) {
|
|
2378
|
+
const maxSize = options?.maxSize ?? 50;
|
|
2379
|
+
const debounceMs = options?.debounce ?? 300;
|
|
2380
|
+
const snapshots = [];
|
|
2381
|
+
let currentIndex = -1;
|
|
2382
|
+
let isUndoRedo = false;
|
|
2383
|
+
let debounceTimer = null;
|
|
2384
|
+
function captureSnapshot() {
|
|
2385
|
+
if (isUndoRedo) return;
|
|
2386
|
+
const snapshot = serializeCanvas(canvas);
|
|
2387
|
+
if (currentIndex < snapshots.length - 1) {
|
|
2388
|
+
snapshots.length = currentIndex + 1;
|
|
2389
|
+
}
|
|
2390
|
+
snapshots.push(snapshot);
|
|
2391
|
+
if (snapshots.length > maxSize) {
|
|
2392
|
+
snapshots.shift();
|
|
2393
|
+
}
|
|
2394
|
+
currentIndex = snapshots.length - 1;
|
|
2395
|
+
}
|
|
2396
|
+
function debouncedCapture() {
|
|
2397
|
+
if (debounceTimer !== null) {
|
|
2398
|
+
clearTimeout(debounceTimer);
|
|
2399
|
+
}
|
|
2400
|
+
debounceTimer = setTimeout(() => {
|
|
2401
|
+
debounceTimer = null;
|
|
2402
|
+
captureSnapshot();
|
|
2403
|
+
}, debounceMs);
|
|
2404
|
+
}
|
|
2405
|
+
const onChange = () => {
|
|
2406
|
+
if (!isUndoRedo) debouncedCapture();
|
|
2407
|
+
};
|
|
2408
|
+
canvas.on("object:added", onChange);
|
|
2409
|
+
canvas.on("object:modified", onChange);
|
|
2410
|
+
canvas.on("object:removed", onChange);
|
|
2411
|
+
async function loadSnapshot(index) {
|
|
2412
|
+
if (index < 0 || index >= snapshots.length) return;
|
|
2413
|
+
isUndoRedo = true;
|
|
2414
|
+
currentIndex = index;
|
|
2415
|
+
try {
|
|
2416
|
+
await loadCanvas(canvas, snapshots[index]);
|
|
2417
|
+
} finally {
|
|
2418
|
+
isUndoRedo = false;
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
return {
|
|
2422
|
+
async undo() {
|
|
2423
|
+
if (currentIndex <= 0) return;
|
|
2424
|
+
await loadSnapshot(currentIndex - 1);
|
|
2425
|
+
},
|
|
2426
|
+
async redo() {
|
|
2427
|
+
if (currentIndex >= snapshots.length - 1) return;
|
|
2428
|
+
await loadSnapshot(currentIndex + 1);
|
|
2429
|
+
},
|
|
2430
|
+
canUndo() {
|
|
2431
|
+
return currentIndex > 0;
|
|
2432
|
+
},
|
|
2433
|
+
canRedo() {
|
|
2434
|
+
return currentIndex < snapshots.length - 1;
|
|
2435
|
+
},
|
|
2436
|
+
pushSnapshot() {
|
|
2437
|
+
if (debounceTimer !== null) {
|
|
2438
|
+
clearTimeout(debounceTimer);
|
|
2439
|
+
debounceTimer = null;
|
|
2440
|
+
}
|
|
2441
|
+
captureSnapshot();
|
|
2442
|
+
},
|
|
2443
|
+
cleanup() {
|
|
2444
|
+
if (debounceTimer !== null) {
|
|
2445
|
+
clearTimeout(debounceTimer);
|
|
2446
|
+
debounceTimer = null;
|
|
2447
|
+
}
|
|
2448
|
+
canvas.off("object:added", onChange);
|
|
2449
|
+
canvas.off("object:modified", onChange);
|
|
2450
|
+
canvas.off("object:removed", onChange);
|
|
2451
|
+
snapshots.length = 0;
|
|
2452
|
+
currentIndex = -1;
|
|
2453
|
+
}
|
|
2454
|
+
};
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2261
2457
|
// src/hooks/useEditCanvas.ts
|
|
2262
2458
|
function useEditCanvas(options) {
|
|
2263
|
-
const canvasRef = (0,
|
|
2264
|
-
const viewportRef = (0,
|
|
2265
|
-
const alignmentCleanupRef = (0,
|
|
2266
|
-
const rotationSnapCleanupRef = (0,
|
|
2267
|
-
const modeCleanupRef = (0,
|
|
2268
|
-
const vertexEditCleanupRef = (0,
|
|
2269
|
-
const keyboardCleanupRef = (0,
|
|
2270
|
-
const
|
|
2271
|
-
const
|
|
2272
|
-
|
|
2273
|
-
const
|
|
2274
|
-
|
|
2275
|
-
|
|
2459
|
+
const canvasRef = (0, import_react3.useRef)(null);
|
|
2460
|
+
const viewportRef = (0, import_react3.useRef)(null);
|
|
2461
|
+
const alignmentCleanupRef = (0, import_react3.useRef)(null);
|
|
2462
|
+
const rotationSnapCleanupRef = (0, import_react3.useRef)(null);
|
|
2463
|
+
const modeCleanupRef = (0, import_react3.useRef)(null);
|
|
2464
|
+
const vertexEditCleanupRef = (0, import_react3.useRef)(null);
|
|
2465
|
+
const keyboardCleanupRef = (0, import_react3.useRef)(null);
|
|
2466
|
+
const historyRef = (0, import_react3.useRef)(null);
|
|
2467
|
+
const optionsRef = (0, import_react3.useRef)(options);
|
|
2468
|
+
optionsRef.current = options;
|
|
2469
|
+
const savedSelectabilityRef = (0, import_react3.useRef)(
|
|
2470
|
+
/* @__PURE__ */ new WeakMap()
|
|
2471
|
+
);
|
|
2472
|
+
const [zoom, setZoom] = (0, import_react3.useState)(1);
|
|
2473
|
+
const [selected, setSelected] = (0, import_react3.useState)([]);
|
|
2474
|
+
const [viewportMode, setViewportModeState] = (0, import_react3.useState)("select");
|
|
2475
|
+
const [isEditingVertices, setIsEditingVertices] = (0, import_react3.useState)(false);
|
|
2476
|
+
const [isDirty, setIsDirty] = (0, import_react3.useState)(false);
|
|
2477
|
+
const [canUndo, setCanUndo] = (0, import_react3.useState)(false);
|
|
2478
|
+
const [canRedo, setCanRedo] = (0, import_react3.useState)(false);
|
|
2479
|
+
const setMode = (0, import_react3.useCallback)((setup) => {
|
|
2276
2480
|
vertexEditCleanupRef.current?.();
|
|
2277
2481
|
vertexEditCleanupRef.current = null;
|
|
2278
2482
|
setIsEditingVertices(false);
|
|
@@ -2283,10 +2487,17 @@ function useEditCanvas(options) {
|
|
|
2283
2487
|
if (setup === null) {
|
|
2284
2488
|
canvas.selection = true;
|
|
2285
2489
|
canvas.forEachObject((obj) => {
|
|
2286
|
-
|
|
2287
|
-
obj.
|
|
2490
|
+
const saved = savedSelectabilityRef.current.get(obj);
|
|
2491
|
+
obj.selectable = saved?.selectable ?? true;
|
|
2492
|
+
obj.evented = saved?.evented ?? true;
|
|
2288
2493
|
});
|
|
2289
2494
|
} else {
|
|
2495
|
+
canvas.forEachObject((obj) => {
|
|
2496
|
+
savedSelectabilityRef.current.set(obj, {
|
|
2497
|
+
selectable: obj.selectable,
|
|
2498
|
+
evented: obj.evented
|
|
2499
|
+
});
|
|
2500
|
+
});
|
|
2290
2501
|
canvas.selection = false;
|
|
2291
2502
|
canvas.forEachObject((obj) => {
|
|
2292
2503
|
obj.selectable = false;
|
|
@@ -2298,37 +2509,41 @@ function useEditCanvas(options) {
|
|
|
2298
2509
|
}
|
|
2299
2510
|
}
|
|
2300
2511
|
}, []);
|
|
2301
|
-
const onReady = (0,
|
|
2512
|
+
const onReady = (0, import_react3.useCallback)(
|
|
2302
2513
|
(canvas) => {
|
|
2303
2514
|
canvasRef.current = canvas;
|
|
2304
|
-
|
|
2515
|
+
const opts = optionsRef.current;
|
|
2516
|
+
if (opts?.scaledStrokes !== false) {
|
|
2305
2517
|
enableScaledStrokes(canvas);
|
|
2306
2518
|
}
|
|
2307
|
-
|
|
2308
|
-
|
|
2519
|
+
if (opts?.borderRadius !== false) {
|
|
2520
|
+
const borderRadiusOpts = typeof opts?.borderRadius === "number" ? { radius: opts.borderRadius } : void 0;
|
|
2521
|
+
enableScaledBorderRadius(canvas, borderRadiusOpts);
|
|
2522
|
+
}
|
|
2523
|
+
if (opts?.keyboardShortcuts !== false) {
|
|
2309
2524
|
keyboardCleanupRef.current = enableKeyboardShortcuts(canvas);
|
|
2310
2525
|
}
|
|
2311
|
-
setCanvasAlignmentEnabled(canvas,
|
|
2312
|
-
if (
|
|
2526
|
+
setCanvasAlignmentEnabled(canvas, opts?.enableAlignment);
|
|
2527
|
+
if (opts?.panAndZoom !== false) {
|
|
2313
2528
|
viewportRef.current = enablePanAndZoom(
|
|
2314
2529
|
canvas,
|
|
2315
|
-
typeof
|
|
2530
|
+
typeof opts?.panAndZoom === "object" ? opts.panAndZoom : void 0
|
|
2316
2531
|
);
|
|
2317
2532
|
}
|
|
2318
2533
|
const alignmentEnabled = resolveAlignmentEnabled(
|
|
2319
|
-
|
|
2320
|
-
|
|
2534
|
+
opts?.enableAlignment,
|
|
2535
|
+
opts?.alignment
|
|
2321
2536
|
);
|
|
2322
2537
|
if (alignmentEnabled) {
|
|
2323
2538
|
alignmentCleanupRef.current = enableObjectAlignment(
|
|
2324
2539
|
canvas,
|
|
2325
|
-
typeof
|
|
2540
|
+
typeof opts?.alignment === "object" ? opts.alignment : void 0
|
|
2326
2541
|
);
|
|
2327
2542
|
}
|
|
2328
|
-
if (
|
|
2543
|
+
if (opts?.rotationSnap !== false) {
|
|
2329
2544
|
rotationSnapCleanupRef.current = enableRotationSnap(
|
|
2330
2545
|
canvas,
|
|
2331
|
-
typeof
|
|
2546
|
+
typeof opts?.rotationSnap === "object" ? opts.rotationSnap : void 0
|
|
2332
2547
|
);
|
|
2333
2548
|
}
|
|
2334
2549
|
canvas.on("mouse:wheel", () => {
|
|
@@ -2343,44 +2558,63 @@ function useEditCanvas(options) {
|
|
|
2343
2558
|
canvas.on("selection:cleared", () => {
|
|
2344
2559
|
setSelected([]);
|
|
2345
2560
|
});
|
|
2346
|
-
if (
|
|
2561
|
+
if (opts?.trackChanges) {
|
|
2347
2562
|
canvas.on("object:added", () => setIsDirty(true));
|
|
2348
2563
|
canvas.on("object:removed", () => setIsDirty(true));
|
|
2349
2564
|
canvas.on("object:modified", () => setIsDirty(true));
|
|
2350
2565
|
}
|
|
2351
|
-
if (
|
|
2352
|
-
const
|
|
2566
|
+
if (opts?.history) {
|
|
2567
|
+
const syncHistoryState = () => {
|
|
2568
|
+
const h = historyRef.current;
|
|
2569
|
+
if (!h) return;
|
|
2570
|
+
setTimeout(() => {
|
|
2571
|
+
setCanUndo(h.canUndo());
|
|
2572
|
+
setCanRedo(h.canRedo());
|
|
2573
|
+
}, 350);
|
|
2574
|
+
};
|
|
2575
|
+
canvas.on("object:added", syncHistoryState);
|
|
2576
|
+
canvas.on("object:removed", syncHistoryState);
|
|
2577
|
+
canvas.on("object:modified", syncHistoryState);
|
|
2578
|
+
}
|
|
2579
|
+
if (opts?.vertexEdit !== false) {
|
|
2580
|
+
const vertexOpts = typeof opts?.vertexEdit === "object" ? opts.vertexEdit : void 0;
|
|
2353
2581
|
canvas.on("mouse:dblclick", (e) => {
|
|
2354
2582
|
if (e.target && e.target instanceof import_fabric17.Polygon) {
|
|
2355
2583
|
vertexEditCleanupRef.current?.();
|
|
2356
|
-
vertexEditCleanupRef.current = enableVertexEdit(
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
vertexOpts,
|
|
2360
|
-
() => {
|
|
2584
|
+
vertexEditCleanupRef.current = enableVertexEdit(canvas, e.target, {
|
|
2585
|
+
...vertexOpts,
|
|
2586
|
+
onExit: () => {
|
|
2361
2587
|
vertexEditCleanupRef.current = null;
|
|
2362
2588
|
setIsEditingVertices(false);
|
|
2363
2589
|
}
|
|
2364
|
-
);
|
|
2590
|
+
});
|
|
2365
2591
|
setIsEditingVertices(true);
|
|
2366
2592
|
}
|
|
2367
2593
|
});
|
|
2368
2594
|
}
|
|
2369
|
-
|
|
2370
|
-
|
|
2595
|
+
if (opts?.history) {
|
|
2596
|
+
const historyOpts = typeof opts.history === "object" ? opts.history : void 0;
|
|
2597
|
+
historyRef.current = createHistoryTracker(canvas, historyOpts);
|
|
2598
|
+
}
|
|
2599
|
+
const onReadyResult = opts?.onReady?.(canvas);
|
|
2600
|
+
if (opts?.autoFitToBackground !== false) {
|
|
2371
2601
|
Promise.resolve(onReadyResult).then(() => {
|
|
2372
2602
|
if (canvas.backgroundImage) {
|
|
2373
2603
|
fitViewportToBackground(canvas);
|
|
2374
2604
|
syncZoom(canvasRef, setZoom);
|
|
2375
2605
|
}
|
|
2606
|
+
historyRef.current?.pushSnapshot();
|
|
2607
|
+
});
|
|
2608
|
+
} else {
|
|
2609
|
+
Promise.resolve(onReadyResult).then(() => {
|
|
2610
|
+
historyRef.current?.pushSnapshot();
|
|
2376
2611
|
});
|
|
2377
2612
|
}
|
|
2378
2613
|
},
|
|
2379
2614
|
// onReady and panAndZoom are intentionally excluded — we only initialize once
|
|
2380
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2381
2615
|
[]
|
|
2382
2616
|
);
|
|
2383
|
-
(0,
|
|
2617
|
+
(0, import_react3.useEffect)(() => {
|
|
2384
2618
|
const canvas = canvasRef.current;
|
|
2385
2619
|
if (!canvas) return;
|
|
2386
2620
|
setCanvasAlignmentEnabled(canvas, options?.enableAlignment);
|
|
@@ -2398,28 +2632,24 @@ function useEditCanvas(options) {
|
|
|
2398
2632
|
alignmentCleanupRef.current = null;
|
|
2399
2633
|
}
|
|
2400
2634
|
}, [options?.enableAlignment]);
|
|
2401
|
-
const setViewportMode = (0,
|
|
2635
|
+
const setViewportMode = (0, import_react3.useCallback)((mode) => {
|
|
2402
2636
|
viewportRef.current?.setMode(mode);
|
|
2403
2637
|
setViewportModeState(mode);
|
|
2404
2638
|
}, []);
|
|
2405
|
-
const { resetViewport: resetViewport2, zoomIn, zoomOut, panToObject } =
|
|
2406
|
-
|
|
2407
|
-
viewportRef,
|
|
2408
|
-
setZoom
|
|
2409
|
-
);
|
|
2410
|
-
const setBackground = (0, import_react2.useCallback)(
|
|
2639
|
+
const { resetViewport: resetViewport2, zoomIn, zoomOut, panToObject, zoomToFit } = useViewportActions(canvasRef, viewportRef, setZoom);
|
|
2640
|
+
const setBackground = (0, import_react3.useCallback)(
|
|
2411
2641
|
async (url, bgOpts) => {
|
|
2412
2642
|
const canvas = canvasRef.current;
|
|
2413
2643
|
if (!canvas) throw new Error("Canvas not ready");
|
|
2414
|
-
const
|
|
2644
|
+
const opts = optionsRef.current;
|
|
2645
|
+
const resizeOpts = opts?.backgroundResize !== false ? typeof opts?.backgroundResize === "object" ? { ...opts.backgroundResize, ...bgOpts } : { ...bgOpts } : bgOpts?.preserveContrast ? { preserveContrast: true } : void 0;
|
|
2415
2646
|
const img = await setBackgroundImage(canvas, url, resizeOpts);
|
|
2416
|
-
if (
|
|
2647
|
+
if (opts?.autoFitToBackground !== false) {
|
|
2417
2648
|
fitViewportToBackground(canvas);
|
|
2418
2649
|
syncZoom(canvasRef, setZoom);
|
|
2419
2650
|
}
|
|
2420
2651
|
return img;
|
|
2421
2652
|
},
|
|
2422
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2423
2653
|
[]
|
|
2424
2654
|
);
|
|
2425
2655
|
return {
|
|
@@ -2444,7 +2674,9 @@ function useEditCanvas(options) {
|
|
|
2444
2674
|
/** Zoom out from the canvas center. Default step: 0.2. */
|
|
2445
2675
|
zoomOut,
|
|
2446
2676
|
/** Pan the viewport to center on a specific object. */
|
|
2447
|
-
panToObject
|
|
2677
|
+
panToObject,
|
|
2678
|
+
/** Zoom and pan to fit a specific object in the viewport. */
|
|
2679
|
+
zoomToFit
|
|
2448
2680
|
},
|
|
2449
2681
|
/** Whether vertex edit mode is currently active (reactive). */
|
|
2450
2682
|
isEditingVertices,
|
|
@@ -2476,12 +2708,32 @@ function useEditCanvas(options) {
|
|
|
2476
2708
|
/** Whether the canvas has been modified since the last `resetDirty()` call. Requires `trackChanges: true`. */
|
|
2477
2709
|
isDirty,
|
|
2478
2710
|
/** Reset the dirty flag (e.g., after a successful save). */
|
|
2479
|
-
resetDirty: (0,
|
|
2711
|
+
resetDirty: (0, import_react3.useCallback)(() => setIsDirty(false), []),
|
|
2712
|
+
/** Undo the last change. Requires `history: true`. */
|
|
2713
|
+
undo: (0, import_react3.useCallback)(async () => {
|
|
2714
|
+
const h = historyRef.current;
|
|
2715
|
+
if (!h) return;
|
|
2716
|
+
await h.undo();
|
|
2717
|
+
setCanUndo(h.canUndo());
|
|
2718
|
+
setCanRedo(h.canRedo());
|
|
2719
|
+
}, []),
|
|
2720
|
+
/** Redo a previously undone change. Requires `history: true`. */
|
|
2721
|
+
redo: (0, import_react3.useCallback)(async () => {
|
|
2722
|
+
const h = historyRef.current;
|
|
2723
|
+
if (!h) return;
|
|
2724
|
+
await h.redo();
|
|
2725
|
+
setCanUndo(h.canUndo());
|
|
2726
|
+
setCanRedo(h.canRedo());
|
|
2727
|
+
}, []),
|
|
2728
|
+
/** Whether an undo operation is available (reactive). Requires `history: true`. */
|
|
2729
|
+
canUndo,
|
|
2730
|
+
/** Whether a redo operation is available (reactive). Requires `history: true`. */
|
|
2731
|
+
canRedo
|
|
2480
2732
|
};
|
|
2481
2733
|
}
|
|
2482
2734
|
|
|
2483
2735
|
// src/hooks/useViewCanvas.ts
|
|
2484
|
-
var
|
|
2736
|
+
var import_react4 = require("react");
|
|
2485
2737
|
function lockCanvas(canvas) {
|
|
2486
2738
|
canvas.selection = false;
|
|
2487
2739
|
canvas.forEachObject((obj) => {
|
|
@@ -2489,18 +2741,24 @@ function lockCanvas(canvas) {
|
|
|
2489
2741
|
});
|
|
2490
2742
|
}
|
|
2491
2743
|
function useViewCanvas(options) {
|
|
2492
|
-
const canvasRef = (0,
|
|
2493
|
-
const viewportRef = (0,
|
|
2494
|
-
const
|
|
2495
|
-
|
|
2744
|
+
const canvasRef = (0, import_react4.useRef)(null);
|
|
2745
|
+
const viewportRef = (0, import_react4.useRef)(null);
|
|
2746
|
+
const optionsRef = (0, import_react4.useRef)(options);
|
|
2747
|
+
optionsRef.current = options;
|
|
2748
|
+
const [zoom, setZoom] = (0, import_react4.useState)(1);
|
|
2749
|
+
const onReady = (0, import_react4.useCallback)(
|
|
2496
2750
|
(canvas) => {
|
|
2497
2751
|
canvasRef.current = canvas;
|
|
2498
|
-
|
|
2752
|
+
const opts = optionsRef.current;
|
|
2753
|
+
if (opts?.scaledStrokes !== false) {
|
|
2499
2754
|
enableScaledStrokes(canvas);
|
|
2500
2755
|
}
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2756
|
+
if (opts?.borderRadius !== false) {
|
|
2757
|
+
const borderRadiusOpts = typeof opts?.borderRadius === "number" ? { radius: opts.borderRadius } : void 0;
|
|
2758
|
+
enableScaledBorderRadius(canvas, borderRadiusOpts);
|
|
2759
|
+
}
|
|
2760
|
+
if (opts?.panAndZoom !== false) {
|
|
2761
|
+
const panAndZoomOpts = typeof opts?.panAndZoom === "object" ? opts.panAndZoom : {};
|
|
2504
2762
|
viewportRef.current = enablePanAndZoom(canvas, {
|
|
2505
2763
|
...panAndZoomOpts,
|
|
2506
2764
|
initialMode: "pan"
|
|
@@ -2513,8 +2771,8 @@ function useViewCanvas(options) {
|
|
|
2513
2771
|
canvas.on("mouse:wheel", () => {
|
|
2514
2772
|
setZoom(canvas.getZoom());
|
|
2515
2773
|
});
|
|
2516
|
-
const onReadyResult =
|
|
2517
|
-
if (
|
|
2774
|
+
const onReadyResult = opts?.onReady?.(canvas);
|
|
2775
|
+
if (opts?.autoFitToBackground !== false) {
|
|
2518
2776
|
Promise.resolve(onReadyResult).then(() => {
|
|
2519
2777
|
if (canvas.backgroundImage) {
|
|
2520
2778
|
fitViewportToBackground(canvas);
|
|
@@ -2524,26 +2782,21 @@ function useViewCanvas(options) {
|
|
|
2524
2782
|
}
|
|
2525
2783
|
},
|
|
2526
2784
|
// onReady and panAndZoom are intentionally excluded — we only initialize once
|
|
2527
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2528
2785
|
[]
|
|
2529
2786
|
);
|
|
2530
|
-
const { resetViewport: resetViewport2, zoomIn, zoomOut, panToObject } =
|
|
2531
|
-
canvasRef,
|
|
2532
|
-
viewportRef,
|
|
2533
|
-
setZoom
|
|
2534
|
-
);
|
|
2787
|
+
const { resetViewport: resetViewport2, zoomIn, zoomOut, panToObject, zoomToFit } = useViewportActions(canvasRef, viewportRef, setZoom);
|
|
2535
2788
|
const findObject = (id) => {
|
|
2536
2789
|
const c = canvasRef.current;
|
|
2537
2790
|
if (!c) return void 0;
|
|
2538
2791
|
return c.getObjects().find((o) => o.data?.id === id);
|
|
2539
2792
|
};
|
|
2540
|
-
const setObjectStyle = (0,
|
|
2793
|
+
const setObjectStyle = (0, import_react4.useCallback)((id, style) => {
|
|
2541
2794
|
const obj = findObject(id);
|
|
2542
2795
|
if (!obj) return;
|
|
2543
2796
|
obj.set(style);
|
|
2544
2797
|
canvasRef.current.requestRenderAll();
|
|
2545
2798
|
}, []);
|
|
2546
|
-
const setObjectStyles = (0,
|
|
2799
|
+
const setObjectStyles = (0, import_react4.useCallback)(
|
|
2547
2800
|
(styles) => {
|
|
2548
2801
|
const c = canvasRef.current;
|
|
2549
2802
|
if (!c) return;
|
|
@@ -2564,7 +2817,7 @@ function useViewCanvas(options) {
|
|
|
2564
2817
|
},
|
|
2565
2818
|
[]
|
|
2566
2819
|
);
|
|
2567
|
-
const setObjectStyleByType = (0,
|
|
2820
|
+
const setObjectStyleByType = (0, import_react4.useCallback)(
|
|
2568
2821
|
(type, style) => {
|
|
2569
2822
|
const c = canvasRef.current;
|
|
2570
2823
|
if (!c) return;
|
|
@@ -2595,7 +2848,9 @@ function useViewCanvas(options) {
|
|
|
2595
2848
|
/** Zoom out from the canvas center. Default step: 0.2. */
|
|
2596
2849
|
zoomOut,
|
|
2597
2850
|
/** Pan the viewport to center on a specific object. */
|
|
2598
|
-
panToObject
|
|
2851
|
+
panToObject,
|
|
2852
|
+
/** Zoom and pan to fit a specific object in the viewport. */
|
|
2853
|
+
zoomToFit
|
|
2599
2854
|
},
|
|
2600
2855
|
/** Update a single object's visual style by its `data.id`. */
|
|
2601
2856
|
setObjectStyle,
|
|
@@ -2607,11 +2862,11 @@ function useViewCanvas(options) {
|
|
|
2607
2862
|
}
|
|
2608
2863
|
|
|
2609
2864
|
// src/hooks/useCanvasEvents.ts
|
|
2610
|
-
var
|
|
2865
|
+
var import_react5 = require("react");
|
|
2611
2866
|
function useCanvasEvents(canvasRef, events) {
|
|
2612
|
-
const eventsRef = (0,
|
|
2867
|
+
const eventsRef = (0, import_react5.useRef)(events);
|
|
2613
2868
|
eventsRef.current = events;
|
|
2614
|
-
(0,
|
|
2869
|
+
(0, import_react5.useEffect)(() => {
|
|
2615
2870
|
const canvas = canvasRef.current;
|
|
2616
2871
|
if (!canvas) return;
|
|
2617
2872
|
const wrappers = /* @__PURE__ */ new Map();
|
|
@@ -2632,17 +2887,17 @@ function useCanvasEvents(canvasRef, events) {
|
|
|
2632
2887
|
}
|
|
2633
2888
|
|
|
2634
2889
|
// src/hooks/useCanvasTooltip.ts
|
|
2635
|
-
var
|
|
2890
|
+
var import_react6 = require("react");
|
|
2636
2891
|
function useCanvasTooltip(canvasRef, options) {
|
|
2637
|
-
const [state, setState] = (0,
|
|
2892
|
+
const [state, setState] = (0, import_react6.useState)({
|
|
2638
2893
|
visible: false,
|
|
2639
2894
|
content: null,
|
|
2640
2895
|
position: { x: 0, y: 0 }
|
|
2641
2896
|
});
|
|
2642
|
-
const hoveredObjectRef = (0,
|
|
2643
|
-
const optionsRef = (0,
|
|
2897
|
+
const hoveredObjectRef = (0, import_react6.useRef)(null);
|
|
2898
|
+
const optionsRef = (0, import_react6.useRef)(options);
|
|
2644
2899
|
optionsRef.current = options;
|
|
2645
|
-
(0,
|
|
2900
|
+
(0, import_react6.useEffect)(() => {
|
|
2646
2901
|
const canvas = canvasRef.current;
|
|
2647
2902
|
if (!canvas) return;
|
|
2648
2903
|
function calculatePosition(target) {
|
|
@@ -2696,13 +2951,13 @@ function useCanvasTooltip(canvasRef, options) {
|
|
|
2696
2951
|
}
|
|
2697
2952
|
|
|
2698
2953
|
// src/hooks/useCanvasClick.ts
|
|
2699
|
-
var
|
|
2954
|
+
var import_react7 = require("react");
|
|
2700
2955
|
function useCanvasClick(canvasRef, onClick, options) {
|
|
2701
|
-
const onClickRef = (0,
|
|
2956
|
+
const onClickRef = (0, import_react7.useRef)(onClick);
|
|
2702
2957
|
onClickRef.current = onClick;
|
|
2703
|
-
const optionsRef = (0,
|
|
2958
|
+
const optionsRef = (0, import_react7.useRef)(options);
|
|
2704
2959
|
optionsRef.current = options;
|
|
2705
|
-
(0,
|
|
2960
|
+
(0, import_react7.useEffect)(() => {
|
|
2706
2961
|
const canvas = canvasRef.current;
|
|
2707
2962
|
if (!canvas) return;
|
|
2708
2963
|
let mouseDown = null;
|
|
@@ -2747,13 +3002,13 @@ function useCanvasClick(canvasRef, onClick, options) {
|
|
|
2747
3002
|
}
|
|
2748
3003
|
|
|
2749
3004
|
// src/hooks/useObjectOverlay.ts
|
|
2750
|
-
var
|
|
3005
|
+
var import_react8 = require("react");
|
|
2751
3006
|
var import_fabric18 = require("fabric");
|
|
2752
3007
|
function useObjectOverlay(canvasRef, object, options) {
|
|
2753
|
-
const containerRef = (0,
|
|
2754
|
-
const optionsRef = (0,
|
|
3008
|
+
const containerRef = (0, import_react8.useRef)(null);
|
|
3009
|
+
const optionsRef = (0, import_react8.useRef)(options);
|
|
2755
3010
|
optionsRef.current = options;
|
|
2756
|
-
(0,
|
|
3011
|
+
(0, import_react8.useEffect)(() => {
|
|
2757
3012
|
const canvas = canvasRef.current;
|
|
2758
3013
|
if (!canvas || !object) return;
|
|
2759
3014
|
function update() {
|
|
@@ -2768,36 +3023,41 @@ function useObjectOverlay(canvasRef, object, options) {
|
|
|
2768
3023
|
const screenCoords = import_fabric18.util.transformPoint(center, vt);
|
|
2769
3024
|
const screenWidth = actualWidth * zoom;
|
|
2770
3025
|
const screenHeight = actualHeight * zoom;
|
|
2771
|
-
el.style.left = `${screenCoords.x - screenWidth / 2}px`;
|
|
2772
|
-
el.style.top = `${screenCoords.y - screenHeight / 2}px`;
|
|
2773
|
-
el.style.width = `${screenWidth}px`;
|
|
2774
|
-
el.style.height = `${screenHeight}px`;
|
|
2775
3026
|
const angle = object.angle ?? 0;
|
|
2776
|
-
el.style.rotate = angle !== 0 ? `${angle}deg` : "";
|
|
2777
3027
|
const opts = optionsRef.current;
|
|
2778
|
-
if (opts?.autoScaleContent) {
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
el.style.
|
|
2782
|
-
|
|
2783
|
-
|
|
3028
|
+
if (opts?.autoScaleContent !== false) {
|
|
3029
|
+
el.style.left = `${screenCoords.x - actualWidth / 2}px`;
|
|
3030
|
+
el.style.top = `${screenCoords.y - actualHeight / 2}px`;
|
|
3031
|
+
el.style.width = `${actualWidth}px`;
|
|
3032
|
+
el.style.height = `${actualHeight}px`;
|
|
3033
|
+
el.style.transformOrigin = "center center";
|
|
3034
|
+
el.style.transform = angle !== 0 ? `scale(${zoom}) rotate(${angle}deg)` : `scale(${zoom})`;
|
|
3035
|
+
el.style.rotate = "";
|
|
3036
|
+
el.style.setProperty("--overlay-scale", String(zoom));
|
|
3037
|
+
if (opts?.textSelector) {
|
|
3038
|
+
const textMinScale = opts?.textMinScale ?? 0.5;
|
|
2784
3039
|
const textEls = el.querySelectorAll(opts.textSelector);
|
|
2785
|
-
const display =
|
|
3040
|
+
const display = zoom < textMinScale ? "none" : "";
|
|
2786
3041
|
textEls.forEach((t) => {
|
|
2787
3042
|
t.style.display = display;
|
|
2788
3043
|
});
|
|
2789
3044
|
}
|
|
3045
|
+
} else {
|
|
3046
|
+
el.style.left = `${screenCoords.x - screenWidth / 2}px`;
|
|
3047
|
+
el.style.top = `${screenCoords.y - screenHeight / 2}px`;
|
|
3048
|
+
el.style.width = `${screenWidth}px`;
|
|
3049
|
+
el.style.height = `${screenHeight}px`;
|
|
3050
|
+
el.style.transform = "";
|
|
3051
|
+
el.style.rotate = angle !== 0 ? `${angle}deg` : "";
|
|
2790
3052
|
}
|
|
2791
3053
|
}
|
|
2792
3054
|
update();
|
|
2793
3055
|
canvas.on("after:render", update);
|
|
2794
|
-
canvas.on("mouse:wheel", update);
|
|
2795
3056
|
object.on("moving", update);
|
|
2796
3057
|
object.on("scaling", update);
|
|
2797
3058
|
object.on("rotating", update);
|
|
2798
3059
|
return () => {
|
|
2799
3060
|
canvas.off("after:render", update);
|
|
2800
|
-
canvas.off("mouse:wheel", update);
|
|
2801
3061
|
object.off("moving", update);
|
|
2802
3062
|
object.off("scaling", update);
|
|
2803
3063
|
object.off("rotating", update);
|
|
@@ -2825,6 +3085,7 @@ var import_fabric19 = require("fabric");
|
|
|
2825
3085
|
Rect,
|
|
2826
3086
|
createCircle,
|
|
2827
3087
|
createCircleAtPoint,
|
|
3088
|
+
createHistoryTracker,
|
|
2828
3089
|
createPolygon,
|
|
2829
3090
|
createPolygonAtPoint,
|
|
2830
3091
|
createPolygonFromDrag,
|