@bwp-web/canvas 0.6.1 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/alignment/cursorSnapping.d.ts.map +1 -1
- package/dist/alignment/objectAlignment.d.ts.map +1 -1
- package/dist/background.d.ts.map +1 -1
- package/dist/constants.d.ts +9 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/hooks/useEditCanvas.d.ts.map +1 -1
- package/dist/index.cjs +248 -206
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +248 -206
- package/dist/index.js.map +1 -1
- package/dist/serialization.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/alignment/objectAlignmentRendering.d.ts +0 -21
- package/dist/alignment/objectAlignmentRendering.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -102,6 +102,10 @@ var DEFAULT_VIEWPORT_PADDING = 0.05;
|
|
|
102
102
|
var BASE_CANVAS_SIZE = 1e3;
|
|
103
103
|
var DEFAULT_SNAP_MARGIN = 6;
|
|
104
104
|
var DEFAULT_ANGLE_SNAP_INTERVAL = 15;
|
|
105
|
+
function computeSnapMargin(canvasWidth, canvasHeight, zoom, baseMargin = DEFAULT_SNAP_MARGIN, scaleWithCanvasSize = true) {
|
|
106
|
+
const sizeScale = scaleWithCanvasSize ? Math.max(canvasWidth || 800, canvasHeight || 600) / BASE_CANVAS_SIZE : 1;
|
|
107
|
+
return baseMargin * sizeScale / zoom;
|
|
108
|
+
}
|
|
105
109
|
var MIN_DRAG_SIZE = 3;
|
|
106
110
|
var POLYGON_CLOSE_THRESHOLD = 10;
|
|
107
111
|
var DEFAULT_IMAGE_MAX_SIZE = 4096;
|
|
@@ -309,23 +313,23 @@ function enablePanAndZoom(canvas, options) {
|
|
|
309
313
|
const targetX = canvasCenterX - objectCenter.x * zoom;
|
|
310
314
|
const targetY = canvasCenterY - objectCenter.y * zoom;
|
|
311
315
|
if (!panOpts?.animate) {
|
|
312
|
-
const
|
|
313
|
-
if (!
|
|
316
|
+
const viewportTransform2 = canvas.viewportTransform;
|
|
317
|
+
if (!viewportTransform2) return;
|
|
314
318
|
canvas.setViewportTransform([
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
+
viewportTransform2[0],
|
|
320
|
+
viewportTransform2[1],
|
|
321
|
+
viewportTransform2[2],
|
|
322
|
+
viewportTransform2[3],
|
|
319
323
|
targetX,
|
|
320
324
|
targetY
|
|
321
325
|
]);
|
|
322
326
|
return;
|
|
323
327
|
}
|
|
324
328
|
const duration = panOpts.duration ?? 300;
|
|
325
|
-
const
|
|
326
|
-
if (!
|
|
327
|
-
const startX =
|
|
328
|
-
const startY =
|
|
329
|
+
const viewportTransform = canvas.viewportTransform;
|
|
330
|
+
if (!viewportTransform) return;
|
|
331
|
+
const startX = viewportTransform[4];
|
|
332
|
+
const startY = viewportTransform[5];
|
|
329
333
|
const startTime = performance.now();
|
|
330
334
|
function step(now) {
|
|
331
335
|
const elapsed = now - startTime;
|
|
@@ -333,13 +337,13 @@ function enablePanAndZoom(canvas, options) {
|
|
|
333
337
|
const eased = 1 - Math.pow(1 - t, 3);
|
|
334
338
|
const currentX = startX + (targetX - startX) * eased;
|
|
335
339
|
const currentY = startY + (targetY - startY) * eased;
|
|
336
|
-
const
|
|
337
|
-
if (!
|
|
340
|
+
const currentTransform = canvas.viewportTransform;
|
|
341
|
+
if (!currentTransform) return;
|
|
338
342
|
canvas.setViewportTransform([
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
+
currentTransform[0],
|
|
344
|
+
currentTransform[1],
|
|
345
|
+
currentTransform[2],
|
|
346
|
+
currentTransform[3],
|
|
343
347
|
currentX,
|
|
344
348
|
currentY
|
|
345
349
|
]);
|
|
@@ -526,6 +530,7 @@ async function setBackgroundImage(canvas, url, options) {
|
|
|
526
530
|
imageUrl = result.url;
|
|
527
531
|
}
|
|
528
532
|
const img = await FabricImage.fromURL(imageUrl, { crossOrigin: "anonymous" });
|
|
533
|
+
img.set({ left: img.width / 2, top: img.height / 2 });
|
|
529
534
|
canvas.backgroundImage = img;
|
|
530
535
|
if (prevContrast !== void 0 && prevContrast !== 1) {
|
|
531
536
|
setBackgroundContrast(canvas, prevContrast);
|
|
@@ -723,63 +728,6 @@ registerSnapPointExtractor(
|
|
|
723
728
|
// src/alignment/objectAlignment.ts
|
|
724
729
|
import { util as util2 } from "fabric";
|
|
725
730
|
|
|
726
|
-
// src/alignment/objectAlignmentRendering.ts
|
|
727
|
-
function drawAlignmentLine(config, origin, target) {
|
|
728
|
-
const ctx = config.canvas.getTopContext();
|
|
729
|
-
const vt = config.canvas.viewportTransform;
|
|
730
|
-
const zoom = config.canvas.getZoom();
|
|
731
|
-
ctx.save();
|
|
732
|
-
ctx.transform(...vt);
|
|
733
|
-
ctx.lineWidth = config.width / zoom;
|
|
734
|
-
if (config.lineDash) ctx.setLineDash(config.lineDash);
|
|
735
|
-
ctx.strokeStyle = config.color;
|
|
736
|
-
ctx.beginPath();
|
|
737
|
-
ctx.moveTo(origin.x, origin.y);
|
|
738
|
-
ctx.lineTo(target.x, target.y);
|
|
739
|
-
ctx.stroke();
|
|
740
|
-
if (config.lineDash) ctx.setLineDash([]);
|
|
741
|
-
drawXMarker(ctx, origin, config.xSize / zoom);
|
|
742
|
-
drawXMarker(ctx, target, config.xSize / zoom);
|
|
743
|
-
ctx.restore();
|
|
744
|
-
}
|
|
745
|
-
function drawXMarker(ctx, point, size) {
|
|
746
|
-
ctx.save();
|
|
747
|
-
ctx.translate(point.x, point.y);
|
|
748
|
-
ctx.beginPath();
|
|
749
|
-
ctx.moveTo(-size, -size);
|
|
750
|
-
ctx.lineTo(size, size);
|
|
751
|
-
ctx.moveTo(size, -size);
|
|
752
|
-
ctx.lineTo(-size, size);
|
|
753
|
-
ctx.stroke();
|
|
754
|
-
ctx.restore();
|
|
755
|
-
}
|
|
756
|
-
function drawMarkerList(config, lines) {
|
|
757
|
-
const ctx = config.canvas.getTopContext();
|
|
758
|
-
const vt = config.canvas.viewportTransform;
|
|
759
|
-
const zoom = config.canvas.getZoom();
|
|
760
|
-
const markerSize = config.xSize / zoom;
|
|
761
|
-
ctx.save();
|
|
762
|
-
ctx.transform(...vt);
|
|
763
|
-
ctx.lineWidth = config.width / zoom;
|
|
764
|
-
ctx.strokeStyle = config.color;
|
|
765
|
-
for (const item of lines) drawXMarker(ctx, item.target, markerSize);
|
|
766
|
-
ctx.restore();
|
|
767
|
-
}
|
|
768
|
-
function drawVerticalAlignmentLines(config, lines) {
|
|
769
|
-
for (const v of lines) {
|
|
770
|
-
const { origin, target } = JSON.parse(v);
|
|
771
|
-
const from = { x: target.x, y: origin.y };
|
|
772
|
-
drawAlignmentLine(config, from, target);
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
function drawHorizontalAlignmentLines(config, lines) {
|
|
776
|
-
for (const h of lines) {
|
|
777
|
-
const { origin, target } = JSON.parse(h);
|
|
778
|
-
const from = { x: origin.x, y: target.y };
|
|
779
|
-
drawAlignmentLine(config, from, target);
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
|
|
783
731
|
// src/alignment/objectAlignmentMath.ts
|
|
784
732
|
var OPPOSITE_ORIGIN_MAP = {
|
|
785
733
|
tl: ["right", "bottom"],
|
|
@@ -903,6 +851,61 @@ function collectHorizontalSnapOffset(props) {
|
|
|
903
851
|
}
|
|
904
852
|
|
|
905
853
|
// src/alignment/objectAlignment.ts
|
|
854
|
+
function drawXMarker(ctx, point, size) {
|
|
855
|
+
ctx.save();
|
|
856
|
+
ctx.translate(point.x, point.y);
|
|
857
|
+
ctx.beginPath();
|
|
858
|
+
ctx.moveTo(-size, -size);
|
|
859
|
+
ctx.lineTo(size, size);
|
|
860
|
+
ctx.moveTo(size, -size);
|
|
861
|
+
ctx.lineTo(-size, size);
|
|
862
|
+
ctx.stroke();
|
|
863
|
+
ctx.restore();
|
|
864
|
+
}
|
|
865
|
+
function drawAlignmentLine(config, origin, target) {
|
|
866
|
+
const ctx = config.canvas.getTopContext();
|
|
867
|
+
const vt = config.canvas.viewportTransform;
|
|
868
|
+
const zoom = config.canvas.getZoom();
|
|
869
|
+
ctx.save();
|
|
870
|
+
ctx.transform(...vt);
|
|
871
|
+
ctx.lineWidth = config.width / zoom;
|
|
872
|
+
if (config.lineDash) ctx.setLineDash(config.lineDash);
|
|
873
|
+
ctx.strokeStyle = config.color;
|
|
874
|
+
ctx.beginPath();
|
|
875
|
+
ctx.moveTo(origin.x, origin.y);
|
|
876
|
+
ctx.lineTo(target.x, target.y);
|
|
877
|
+
ctx.stroke();
|
|
878
|
+
if (config.lineDash) ctx.setLineDash([]);
|
|
879
|
+
drawXMarker(ctx, origin, config.xSize / zoom);
|
|
880
|
+
drawXMarker(ctx, target, config.xSize / zoom);
|
|
881
|
+
ctx.restore();
|
|
882
|
+
}
|
|
883
|
+
function drawMarkerList(config, lines) {
|
|
884
|
+
const ctx = config.canvas.getTopContext();
|
|
885
|
+
const vt = config.canvas.viewportTransform;
|
|
886
|
+
const zoom = config.canvas.getZoom();
|
|
887
|
+
const markerSize = config.xSize / zoom;
|
|
888
|
+
ctx.save();
|
|
889
|
+
ctx.transform(...vt);
|
|
890
|
+
ctx.lineWidth = config.width / zoom;
|
|
891
|
+
ctx.strokeStyle = config.color;
|
|
892
|
+
for (const item of lines) drawXMarker(ctx, item.target, markerSize);
|
|
893
|
+
ctx.restore();
|
|
894
|
+
}
|
|
895
|
+
function drawVerticalAlignmentLines(config, lines) {
|
|
896
|
+
for (const v of lines) {
|
|
897
|
+
const { origin, target } = JSON.parse(v);
|
|
898
|
+
const from = { x: target.x, y: origin.y };
|
|
899
|
+
drawAlignmentLine(config, from, target);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
function drawHorizontalAlignmentLines(config, lines) {
|
|
903
|
+
for (const h of lines) {
|
|
904
|
+
const { origin, target } = JSON.parse(h);
|
|
905
|
+
const from = { x: origin.x, y: target.y };
|
|
906
|
+
drawAlignmentLine(config, from, target);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
906
909
|
function adjustCornerForFlip(corner, target) {
|
|
907
910
|
let adjusted = corner;
|
|
908
911
|
if (target.flipX) {
|
|
@@ -957,9 +960,13 @@ var ObjectAlignmentGuides = class {
|
|
|
957
960
|
}
|
|
958
961
|
// --- Margin calculation ---
|
|
959
962
|
computeMargin() {
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
+
return computeSnapMargin(
|
|
964
|
+
this.canvas.width ?? 800,
|
|
965
|
+
this.canvas.height ?? 600,
|
|
966
|
+
this.canvas.getZoom(),
|
|
967
|
+
this.margin,
|
|
968
|
+
this.scaleWithCanvasSize
|
|
969
|
+
);
|
|
963
970
|
}
|
|
964
971
|
// --- Snap point caching ---
|
|
965
972
|
getCachedSnapPoints(object) {
|
|
@@ -1095,10 +1102,13 @@ function enableRotationSnap(canvas, options) {
|
|
|
1095
1102
|
// src/alignment/cursorSnapping.ts
|
|
1096
1103
|
import { Point as Point5 } from "fabric";
|
|
1097
1104
|
function snapCursorPoint(canvas, rawPoint, options) {
|
|
1098
|
-
const
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1105
|
+
const margin = computeSnapMargin(
|
|
1106
|
+
canvas.width ?? 800,
|
|
1107
|
+
canvas.height ?? 600,
|
|
1108
|
+
canvas.getZoom(),
|
|
1109
|
+
options?.margin ?? DEFAULT_SNAP_MARGIN,
|
|
1110
|
+
options?.scaleWithCanvasSize !== false
|
|
1111
|
+
);
|
|
1102
1112
|
const exclude = options?.exclude ?? /* @__PURE__ */ new Set();
|
|
1103
1113
|
let targetPoints;
|
|
1104
1114
|
if (options?.targetPoints) {
|
|
@@ -2161,20 +2171,7 @@ function enableScaledBorderRadius(canvas, options) {
|
|
|
2161
2171
|
function getBaseStrokeWidth(obj) {
|
|
2162
2172
|
return strokeBaseMap.get(obj) ?? obj.strokeWidth ?? 0;
|
|
2163
2173
|
}
|
|
2164
|
-
function
|
|
2165
|
-
const properties = [
|
|
2166
|
-
"data",
|
|
2167
|
-
"shapeType",
|
|
2168
|
-
// Control styling — absent from Fabric's default toObject output
|
|
2169
|
-
"borderColor",
|
|
2170
|
-
"cornerColor",
|
|
2171
|
-
"cornerStrokeColor",
|
|
2172
|
-
"transparentCorners",
|
|
2173
|
-
// Interaction locks — absent from Fabric's default toObject output
|
|
2174
|
-
"lockRotation",
|
|
2175
|
-
"lockUniScaling",
|
|
2176
|
-
...options?.properties ?? []
|
|
2177
|
-
];
|
|
2174
|
+
function prepareStrokeWidths(canvas) {
|
|
2178
2175
|
const scaledWidths = /* @__PURE__ */ new Map();
|
|
2179
2176
|
canvas.forEachObject((obj) => {
|
|
2180
2177
|
const base = strokeBaseMap.get(obj);
|
|
@@ -2183,6 +2180,11 @@ function serializeCanvas(canvas, options) {
|
|
|
2183
2180
|
obj.strokeWidth = base;
|
|
2184
2181
|
}
|
|
2185
2182
|
});
|
|
2183
|
+
return () => scaledWidths.forEach((scaled, obj) => {
|
|
2184
|
+
obj.strokeWidth = scaled;
|
|
2185
|
+
});
|
|
2186
|
+
}
|
|
2187
|
+
function prepareBorderRadii(canvas) {
|
|
2186
2188
|
const appliedRadii = /* @__PURE__ */ new Map();
|
|
2187
2189
|
canvas.forEachObject((obj) => {
|
|
2188
2190
|
if (!(obj instanceof Rect5)) return;
|
|
@@ -2192,6 +2194,11 @@ function serializeCanvas(canvas, options) {
|
|
|
2192
2194
|
obj.set({ rx: base.rx, ry: base.ry });
|
|
2193
2195
|
}
|
|
2194
2196
|
});
|
|
2197
|
+
return () => appliedRadii.forEach((radii, obj) => {
|
|
2198
|
+
obj.set(radii);
|
|
2199
|
+
});
|
|
2200
|
+
}
|
|
2201
|
+
function prepareObjectOrigins(canvas) {
|
|
2195
2202
|
const savedOrigins = /* @__PURE__ */ new Map();
|
|
2196
2203
|
canvas.forEachObject((obj) => {
|
|
2197
2204
|
if (obj.originX === "left" && obj.originY === "top") return;
|
|
@@ -2202,20 +2209,36 @@ function serializeCanvas(canvas, options) {
|
|
|
2202
2209
|
top: obj.top ?? 0
|
|
2203
2210
|
});
|
|
2204
2211
|
const leftTop = obj.getPositionByOrigin("left", "top");
|
|
2205
|
-
obj.set({
|
|
2212
|
+
obj.set({
|
|
2213
|
+
originX: "left",
|
|
2214
|
+
originY: "top",
|
|
2215
|
+
left: leftTop.x,
|
|
2216
|
+
top: leftTop.y
|
|
2217
|
+
});
|
|
2206
2218
|
});
|
|
2219
|
+
return () => savedOrigins.forEach((saved, obj) => {
|
|
2220
|
+
obj.set(saved);
|
|
2221
|
+
});
|
|
2222
|
+
}
|
|
2223
|
+
function prepareBackgroundOrigin(canvas) {
|
|
2207
2224
|
const bg = canvas.backgroundImage;
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
savedBgOrigin = {
|
|
2211
|
-
originX: bg.originX,
|
|
2212
|
-
originY: bg.originY,
|
|
2213
|
-
left: bg.left ?? 0,
|
|
2214
|
-
top: bg.top ?? 0
|
|
2225
|
+
if (!(bg instanceof FabricImage2) || bg.originX === "left" && bg.originY === "top") {
|
|
2226
|
+
return () => {
|
|
2215
2227
|
};
|
|
2216
|
-
const leftTop = bg.getPositionByOrigin("left", "top");
|
|
2217
|
-
bg.set({ originX: "left", originY: "top", left: leftTop.x, top: leftTop.y });
|
|
2218
2228
|
}
|
|
2229
|
+
const saved = {
|
|
2230
|
+
originX: bg.originX,
|
|
2231
|
+
originY: bg.originY,
|
|
2232
|
+
left: bg.left ?? 0,
|
|
2233
|
+
top: bg.top ?? 0
|
|
2234
|
+
};
|
|
2235
|
+
const leftTop = bg.getPositionByOrigin("left", "top");
|
|
2236
|
+
bg.set({ originX: "left", originY: "top", left: leftTop.x, top: leftTop.y });
|
|
2237
|
+
return () => {
|
|
2238
|
+
bg.set(saved);
|
|
2239
|
+
};
|
|
2240
|
+
}
|
|
2241
|
+
function prepareStrokeWidthBaseData(canvas) {
|
|
2219
2242
|
const savedData = /* @__PURE__ */ new Map();
|
|
2220
2243
|
canvas.forEachObject((obj) => {
|
|
2221
2244
|
const base = strokeBaseMap.get(obj) ?? obj.strokeWidth;
|
|
@@ -2227,33 +2250,53 @@ function serializeCanvas(canvas, options) {
|
|
|
2227
2250
|
};
|
|
2228
2251
|
}
|
|
2229
2252
|
});
|
|
2253
|
+
return () => savedData.forEach((originalData, obj) => {
|
|
2254
|
+
obj.data = originalData;
|
|
2255
|
+
});
|
|
2256
|
+
}
|
|
2257
|
+
function serializeCanvas(canvas, options) {
|
|
2258
|
+
const properties = [
|
|
2259
|
+
"data",
|
|
2260
|
+
"shapeType",
|
|
2261
|
+
// Control styling — absent from Fabric's default toObject output
|
|
2262
|
+
"borderColor",
|
|
2263
|
+
"cornerColor",
|
|
2264
|
+
"cornerStrokeColor",
|
|
2265
|
+
"transparentCorners",
|
|
2266
|
+
// Interaction locks — absent from Fabric's default toObject output
|
|
2267
|
+
"lockRotation",
|
|
2268
|
+
"lockUniScaling",
|
|
2269
|
+
...options?.properties ?? []
|
|
2270
|
+
];
|
|
2271
|
+
const restoreStrokeWidths = prepareStrokeWidths(canvas);
|
|
2272
|
+
const restoreBorderRadii = prepareBorderRadii(canvas);
|
|
2273
|
+
const restoreOrigins = prepareObjectOrigins(canvas);
|
|
2274
|
+
const restoreBgOrigin = prepareBackgroundOrigin(canvas);
|
|
2275
|
+
const restoreData = prepareStrokeWidthBaseData(canvas);
|
|
2230
2276
|
const json = canvas.toObject(properties);
|
|
2231
2277
|
delete json.backgroundColor;
|
|
2232
2278
|
json.backgroundFilters = {
|
|
2233
2279
|
opacity: getBackgroundContrast(canvas),
|
|
2234
2280
|
inverted: getBackgroundInverted(canvas)
|
|
2235
2281
|
};
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
});
|
|
2239
|
-
appliedRadii.forEach((radii, obj) => {
|
|
2240
|
-
obj.set({ rx: radii.rx, ry: radii.ry });
|
|
2241
|
-
});
|
|
2242
|
-
savedOrigins.forEach((saved, obj) => {
|
|
2243
|
-
obj.set(saved);
|
|
2244
|
-
});
|
|
2245
|
-
if (savedBgOrigin && bg instanceof FabricImage2) {
|
|
2246
|
-
bg.set(savedBgOrigin);
|
|
2282
|
+
if (canvas.lockLightMode !== void 0) {
|
|
2283
|
+
json.lockLightMode = canvas.lockLightMode;
|
|
2247
2284
|
}
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2285
|
+
restoreStrokeWidths();
|
|
2286
|
+
restoreBorderRadii();
|
|
2287
|
+
restoreOrigins();
|
|
2288
|
+
restoreBgOrigin();
|
|
2289
|
+
restoreData();
|
|
2251
2290
|
return json;
|
|
2252
2291
|
}
|
|
2253
2292
|
async function loadCanvas(canvas, json, options) {
|
|
2254
2293
|
await canvas.loadFromJSON(json);
|
|
2255
2294
|
canvas.backgroundColor = "";
|
|
2256
2295
|
delete canvas.backgroundFilters;
|
|
2296
|
+
const rawCanvas = canvas;
|
|
2297
|
+
if (rawCanvas.lockLightMode !== void 0) {
|
|
2298
|
+
canvas.lockLightMode = rawCanvas.lockLightMode;
|
|
2299
|
+
}
|
|
2257
2300
|
const bg = canvas.backgroundImage;
|
|
2258
2301
|
if (bg instanceof FabricImage2) {
|
|
2259
2302
|
if (bg.originX !== "center" || bg.originY !== "center") {
|
|
@@ -2446,105 +2489,104 @@ function useEditCanvas(options) {
|
|
|
2446
2489
|
(canvas) => {
|
|
2447
2490
|
canvasRef.current = canvas;
|
|
2448
2491
|
const opts = optionsRef.current;
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
)
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
typeof opts?.alignment === "object" ? opts.alignment : void 0
|
|
2474
|
-
);
|
|
2475
|
-
}
|
|
2476
|
-
if (opts?.rotationSnap !== false) {
|
|
2477
|
-
rotationSnapCleanupRef.current = enableRotationSnap(
|
|
2478
|
-
canvas,
|
|
2479
|
-
typeof opts?.rotationSnap === "object" ? opts.rotationSnap : void 0
|
|
2492
|
+
setupFeatures();
|
|
2493
|
+
setupEventListeners();
|
|
2494
|
+
invokeOnReady();
|
|
2495
|
+
function setupFeatures() {
|
|
2496
|
+
if (opts?.scaledStrokes !== false) {
|
|
2497
|
+
enableScaledStrokes(canvas);
|
|
2498
|
+
}
|
|
2499
|
+
if (opts?.borderRadius !== false) {
|
|
2500
|
+
const borderRadiusOpts = typeof opts?.borderRadius === "number" ? { radius: opts.borderRadius } : void 0;
|
|
2501
|
+
enableScaledBorderRadius(canvas, borderRadiusOpts);
|
|
2502
|
+
}
|
|
2503
|
+
if (opts?.keyboardShortcuts !== false) {
|
|
2504
|
+
keyboardCleanupRef.current = enableKeyboardShortcuts(canvas);
|
|
2505
|
+
}
|
|
2506
|
+
setCanvasAlignmentEnabled(canvas, opts?.enableAlignment);
|
|
2507
|
+
if (opts?.panAndZoom !== false) {
|
|
2508
|
+
viewportRef.current = enablePanAndZoom(
|
|
2509
|
+
canvas,
|
|
2510
|
+
typeof opts?.panAndZoom === "object" ? opts.panAndZoom : void 0
|
|
2511
|
+
);
|
|
2512
|
+
}
|
|
2513
|
+
const alignmentEnabled = resolveAlignmentEnabled(
|
|
2514
|
+
opts?.enableAlignment,
|
|
2515
|
+
opts?.alignment
|
|
2480
2516
|
);
|
|
2517
|
+
if (alignmentEnabled) {
|
|
2518
|
+
alignmentCleanupRef.current = enableObjectAlignment(
|
|
2519
|
+
canvas,
|
|
2520
|
+
typeof opts?.alignment === "object" ? opts.alignment : void 0
|
|
2521
|
+
);
|
|
2522
|
+
}
|
|
2523
|
+
if (opts?.rotationSnap !== false) {
|
|
2524
|
+
rotationSnapCleanupRef.current = enableRotationSnap(
|
|
2525
|
+
canvas,
|
|
2526
|
+
typeof opts?.rotationSnap === "object" ? opts.rotationSnap : void 0
|
|
2527
|
+
);
|
|
2528
|
+
}
|
|
2529
|
+
if (opts?.history) {
|
|
2530
|
+
const historyOpts = typeof opts.history === "object" ? opts.history : void 0;
|
|
2531
|
+
historyRef.current = createHistoryTracker(canvas, historyOpts);
|
|
2532
|
+
}
|
|
2481
2533
|
}
|
|
2482
|
-
|
|
2483
|
-
setZoom(canvas.getZoom());
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
}
|
|
2526
|
-
});
|
|
2527
|
-
}
|
|
2528
|
-
if (opts?.history) {
|
|
2529
|
-
const historyOpts = typeof opts.history === "object" ? opts.history : void 0;
|
|
2530
|
-
historyRef.current = createHistoryTracker(canvas, historyOpts);
|
|
2534
|
+
function setupEventListeners() {
|
|
2535
|
+
canvas.on("mouse:wheel", () => setZoom(canvas.getZoom()));
|
|
2536
|
+
canvas.on("selection:created", (e) => setSelected(e.selected ?? []));
|
|
2537
|
+
canvas.on("selection:updated", (e) => setSelected(e.selected ?? []));
|
|
2538
|
+
canvas.on("selection:cleared", () => setSelected([]));
|
|
2539
|
+
if (opts?.trackChanges) {
|
|
2540
|
+
canvas.on("object:added", () => setIsDirty(true));
|
|
2541
|
+
canvas.on("object:removed", () => setIsDirty(true));
|
|
2542
|
+
canvas.on("object:modified", () => setIsDirty(true));
|
|
2543
|
+
}
|
|
2544
|
+
if (opts?.history) {
|
|
2545
|
+
const syncHistoryState = () => {
|
|
2546
|
+
const h = historyRef.current;
|
|
2547
|
+
if (!h) return;
|
|
2548
|
+
setTimeout(() => {
|
|
2549
|
+
setCanUndo(h.canUndo());
|
|
2550
|
+
setCanRedo(h.canRedo());
|
|
2551
|
+
}, 350);
|
|
2552
|
+
};
|
|
2553
|
+
canvas.on("object:added", syncHistoryState);
|
|
2554
|
+
canvas.on("object:removed", syncHistoryState);
|
|
2555
|
+
canvas.on("object:modified", syncHistoryState);
|
|
2556
|
+
}
|
|
2557
|
+
if (opts?.vertexEdit !== false) {
|
|
2558
|
+
const vertexOpts = typeof opts?.vertexEdit === "object" ? opts.vertexEdit : void 0;
|
|
2559
|
+
canvas.on("mouse:dblclick", (e) => {
|
|
2560
|
+
if (e.target && e.target instanceof Polygon4) {
|
|
2561
|
+
vertexEditCleanupRef.current?.();
|
|
2562
|
+
vertexEditCleanupRef.current = enableVertexEdit(
|
|
2563
|
+
canvas,
|
|
2564
|
+
e.target,
|
|
2565
|
+
{
|
|
2566
|
+
...vertexOpts,
|
|
2567
|
+
onExit: () => {
|
|
2568
|
+
vertexEditCleanupRef.current = null;
|
|
2569
|
+
setIsEditingVertices(false);
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
);
|
|
2573
|
+
setIsEditingVertices(true);
|
|
2574
|
+
}
|
|
2575
|
+
});
|
|
2576
|
+
}
|
|
2531
2577
|
}
|
|
2532
|
-
|
|
2533
|
-
|
|
2578
|
+
function invokeOnReady() {
|
|
2579
|
+
const onReadyResult = opts?.onReady?.(canvas);
|
|
2534
2580
|
Promise.resolve(onReadyResult).then(() => {
|
|
2535
|
-
if (canvas.backgroundImage) {
|
|
2581
|
+
if (opts?.autoFitToBackground !== false && canvas.backgroundImage) {
|
|
2536
2582
|
fitViewportToBackground(canvas);
|
|
2537
2583
|
syncZoom(canvasRef, setZoom);
|
|
2538
2584
|
}
|
|
2539
2585
|
historyRef.current?.pushSnapshot();
|
|
2540
2586
|
});
|
|
2541
|
-
} else {
|
|
2542
|
-
Promise.resolve(onReadyResult).then(() => {
|
|
2543
|
-
historyRef.current?.pushSnapshot();
|
|
2544
|
-
});
|
|
2545
2587
|
}
|
|
2546
2588
|
},
|
|
2547
|
-
//
|
|
2589
|
+
// Dependency array intentionally empty — we only initialize once on mount
|
|
2548
2590
|
[]
|
|
2549
2591
|
);
|
|
2550
2592
|
useEffect2(() => {
|