@bwp-web/canvas 0.6.0 → 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/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 vt2 = canvas.viewportTransform;
313
- if (!vt2) return;
316
+ const viewportTransform2 = canvas.viewportTransform;
317
+ if (!viewportTransform2) return;
314
318
  canvas.setViewportTransform([
315
- vt2[0],
316
- vt2[1],
317
- vt2[2],
318
- vt2[3],
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 vt = canvas.viewportTransform;
326
- if (!vt) return;
327
- const startX = vt[4];
328
- const startY = vt[5];
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 currentVt = canvas.viewportTransform;
337
- if (!currentVt) return;
340
+ const currentTransform = canvas.viewportTransform;
341
+ if (!currentTransform) return;
338
342
  canvas.setViewportTransform([
339
- currentVt[0],
340
- currentVt[1],
341
- currentVt[2],
342
- currentVt[3],
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
- const zoom = this.canvas.getZoom();
961
- const sizeScale = this.scaleWithCanvasSize ? Math.max(this.canvas.width ?? 800, this.canvas.height ?? 600) / BASE_CANVAS_SIZE : 1;
962
- return this.margin * sizeScale / zoom;
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 zoom = canvas.getZoom();
1099
- const scaleWithSize = options?.scaleWithCanvasSize !== false;
1100
- const sizeScale = scaleWithSize ? Math.max(canvas.width ?? 800, canvas.height ?? 600) / BASE_CANVAS_SIZE : 1;
1101
- const margin = (options?.margin ?? DEFAULT_SNAP_MARGIN) * sizeScale / zoom;
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 serializeCanvas(canvas, options) {
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,20 +2194,109 @@ function serializeCanvas(canvas, options) {
2192
2194
  obj.set({ rx: base.rx, ry: base.ry });
2193
2195
  }
2194
2196
  });
2195
- const json = canvas.toObject(properties);
2196
- delete json.backgroundColor;
2197
- scaledWidths.forEach((scaled, obj) => {
2198
- obj.strokeWidth = scaled;
2197
+ return () => appliedRadii.forEach((radii, obj) => {
2198
+ obj.set(radii);
2199
2199
  });
2200
- appliedRadii.forEach((radii, obj) => {
2201
- obj.set({ rx: radii.rx, ry: radii.ry });
2200
+ }
2201
+ function prepareObjectOrigins(canvas) {
2202
+ const savedOrigins = /* @__PURE__ */ new Map();
2203
+ canvas.forEachObject((obj) => {
2204
+ if (obj.originX === "left" && obj.originY === "top") return;
2205
+ savedOrigins.set(obj, {
2206
+ originX: obj.originX,
2207
+ originY: obj.originY,
2208
+ left: obj.left ?? 0,
2209
+ top: obj.top ?? 0
2210
+ });
2211
+ const leftTop = obj.getPositionByOrigin("left", "top");
2212
+ obj.set({
2213
+ originX: "left",
2214
+ originY: "top",
2215
+ left: leftTop.x,
2216
+ top: leftTop.y
2217
+ });
2218
+ });
2219
+ return () => savedOrigins.forEach((saved, obj) => {
2220
+ obj.set(saved);
2202
2221
  });
2222
+ }
2223
+ function prepareBackgroundOrigin(canvas) {
2224
+ const bg = canvas.backgroundImage;
2225
+ if (!(bg instanceof FabricImage2) || bg.originX === "left" && bg.originY === "top") {
2226
+ return () => {
2227
+ };
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) {
2242
+ const savedData = /* @__PURE__ */ new Map();
2243
+ canvas.forEachObject((obj) => {
2244
+ const base = strokeBaseMap.get(obj) ?? obj.strokeWidth;
2245
+ if (base !== void 0 && base !== 0 && obj.data) {
2246
+ savedData.set(obj, obj.data);
2247
+ obj.data = {
2248
+ ...obj.data,
2249
+ strokeWidthBase: base
2250
+ };
2251
+ }
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);
2276
+ const json = canvas.toObject(properties);
2277
+ delete json.backgroundColor;
2278
+ json.backgroundFilters = {
2279
+ opacity: getBackgroundContrast(canvas),
2280
+ inverted: getBackgroundInverted(canvas)
2281
+ };
2282
+ if (canvas.lockLightMode !== void 0) {
2283
+ json.lockLightMode = canvas.lockLightMode;
2284
+ }
2285
+ restoreStrokeWidths();
2286
+ restoreBorderRadii();
2287
+ restoreOrigins();
2288
+ restoreBgOrigin();
2289
+ restoreData();
2203
2290
  return json;
2204
2291
  }
2205
2292
  async function loadCanvas(canvas, json, options) {
2206
2293
  await canvas.loadFromJSON(json);
2207
2294
  canvas.backgroundColor = "";
2208
2295
  delete canvas.backgroundFilters;
2296
+ const rawCanvas = canvas;
2297
+ if (rawCanvas.lockLightMode !== void 0) {
2298
+ canvas.lockLightMode = rawCanvas.lockLightMode;
2299
+ }
2209
2300
  const bg = canvas.backgroundImage;
2210
2301
  if (bg instanceof FabricImage2) {
2211
2302
  if (bg.originX !== "center" || bg.originY !== "center") {
@@ -2398,105 +2489,104 @@ function useEditCanvas(options) {
2398
2489
  (canvas) => {
2399
2490
  canvasRef.current = canvas;
2400
2491
  const opts = optionsRef.current;
2401
- if (opts?.scaledStrokes !== false) {
2402
- enableScaledStrokes(canvas);
2403
- }
2404
- if (opts?.borderRadius !== false) {
2405
- const borderRadiusOpts = typeof opts?.borderRadius === "number" ? { radius: opts.borderRadius } : void 0;
2406
- enableScaledBorderRadius(canvas, borderRadiusOpts);
2407
- }
2408
- if (opts?.keyboardShortcuts !== false) {
2409
- keyboardCleanupRef.current = enableKeyboardShortcuts(canvas);
2410
- }
2411
- setCanvasAlignmentEnabled(canvas, opts?.enableAlignment);
2412
- if (opts?.panAndZoom !== false) {
2413
- viewportRef.current = enablePanAndZoom(
2414
- canvas,
2415
- typeof opts?.panAndZoom === "object" ? opts.panAndZoom : void 0
2416
- );
2417
- }
2418
- const alignmentEnabled = resolveAlignmentEnabled(
2419
- opts?.enableAlignment,
2420
- opts?.alignment
2421
- );
2422
- if (alignmentEnabled) {
2423
- alignmentCleanupRef.current = enableObjectAlignment(
2424
- canvas,
2425
- typeof opts?.alignment === "object" ? opts.alignment : void 0
2426
- );
2427
- }
2428
- if (opts?.rotationSnap !== false) {
2429
- rotationSnapCleanupRef.current = enableRotationSnap(
2430
- canvas,
2431
- 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
2432
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
+ }
2433
2533
  }
2434
- canvas.on("mouse:wheel", () => {
2435
- setZoom(canvas.getZoom());
2436
- });
2437
- canvas.on("selection:created", (e) => {
2438
- setSelected(e.selected ?? []);
2439
- });
2440
- canvas.on("selection:updated", (e) => {
2441
- setSelected(e.selected ?? []);
2442
- });
2443
- canvas.on("selection:cleared", () => {
2444
- setSelected([]);
2445
- });
2446
- if (opts?.trackChanges) {
2447
- canvas.on("object:added", () => setIsDirty(true));
2448
- canvas.on("object:removed", () => setIsDirty(true));
2449
- canvas.on("object:modified", () => setIsDirty(true));
2450
- }
2451
- if (opts?.history) {
2452
- const syncHistoryState = () => {
2453
- const h = historyRef.current;
2454
- if (!h) return;
2455
- setTimeout(() => {
2456
- setCanUndo(h.canUndo());
2457
- setCanRedo(h.canRedo());
2458
- }, 350);
2459
- };
2460
- canvas.on("object:added", syncHistoryState);
2461
- canvas.on("object:removed", syncHistoryState);
2462
- canvas.on("object:modified", syncHistoryState);
2463
- }
2464
- if (opts?.vertexEdit !== false) {
2465
- const vertexOpts = typeof opts?.vertexEdit === "object" ? opts.vertexEdit : void 0;
2466
- canvas.on("mouse:dblclick", (e) => {
2467
- if (e.target && e.target instanceof Polygon4) {
2468
- vertexEditCleanupRef.current?.();
2469
- vertexEditCleanupRef.current = enableVertexEdit(canvas, e.target, {
2470
- ...vertexOpts,
2471
- onExit: () => {
2472
- vertexEditCleanupRef.current = null;
2473
- setIsEditingVertices(false);
2474
- }
2475
- });
2476
- setIsEditingVertices(true);
2477
- }
2478
- });
2479
- }
2480
- if (opts?.history) {
2481
- const historyOpts = typeof opts.history === "object" ? opts.history : void 0;
2482
- 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
+ }
2483
2577
  }
2484
- const onReadyResult = opts?.onReady?.(canvas);
2485
- if (opts?.autoFitToBackground !== false) {
2578
+ function invokeOnReady() {
2579
+ const onReadyResult = opts?.onReady?.(canvas);
2486
2580
  Promise.resolve(onReadyResult).then(() => {
2487
- if (canvas.backgroundImage) {
2581
+ if (opts?.autoFitToBackground !== false && canvas.backgroundImage) {
2488
2582
  fitViewportToBackground(canvas);
2489
2583
  syncZoom(canvasRef, setZoom);
2490
2584
  }
2491
2585
  historyRef.current?.pushSnapshot();
2492
2586
  });
2493
- } else {
2494
- Promise.resolve(onReadyResult).then(() => {
2495
- historyRef.current?.pushSnapshot();
2496
- });
2497
2587
  }
2498
2588
  },
2499
- // onReady and panAndZoom are intentionally excluded — we only initialize once
2589
+ // Dependency array intentionally empty — we only initialize once on mount
2500
2590
  []
2501
2591
  );
2502
2592
  useEffect2(() => {