@bwp-web/canvas 0.6.1 → 0.7.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/index.cjs CHANGED
@@ -30,6 +30,9 @@ __export(index_exports, {
30
30
  FabricCanvas: () => import_fabric19.Canvas,
31
31
  FabricImage: () => import_fabric19.FabricImage,
32
32
  FabricObject: () => import_fabric19.FabricObject,
33
+ FixedSizeContent: () => FixedSizeContent,
34
+ ObjectOverlay: () => ObjectOverlay,
35
+ OverlayContent: () => OverlayContent,
33
36
  Point: () => import_fabric19.Point,
34
37
  Polygon: () => import_fabric19.Polygon,
35
38
  Rect: () => import_fabric19.Rect,
@@ -75,7 +78,6 @@ __export(index_exports, {
75
78
  useCanvasEvents: () => useCanvasEvents,
76
79
  useCanvasTooltip: () => useCanvasTooltip,
77
80
  useEditCanvas: () => useEditCanvas,
78
- useObjectOverlay: () => useObjectOverlay,
79
81
  useViewCanvas: () => useViewCanvas,
80
82
  util: () => import_fabric19.util
81
83
  });
@@ -183,6 +185,10 @@ var DEFAULT_VIEWPORT_PADDING = 0.05;
183
185
  var BASE_CANVAS_SIZE = 1e3;
184
186
  var DEFAULT_SNAP_MARGIN = 6;
185
187
  var DEFAULT_ANGLE_SNAP_INTERVAL = 15;
188
+ function computeSnapMargin(canvasWidth, canvasHeight, zoom, baseMargin = DEFAULT_SNAP_MARGIN, scaleWithCanvasSize = true) {
189
+ const sizeScale = scaleWithCanvasSize ? Math.max(canvasWidth || 800, canvasHeight || 600) / BASE_CANVAS_SIZE : 1;
190
+ return baseMargin * sizeScale / zoom;
191
+ }
186
192
  var MIN_DRAG_SIZE = 3;
187
193
  var POLYGON_CLOSE_THRESHOLD = 10;
188
194
  var DEFAULT_IMAGE_MAX_SIZE = 4096;
@@ -390,23 +396,23 @@ function enablePanAndZoom(canvas, options) {
390
396
  const targetX = canvasCenterX - objectCenter.x * zoom;
391
397
  const targetY = canvasCenterY - objectCenter.y * zoom;
392
398
  if (!panOpts?.animate) {
393
- const vt2 = canvas.viewportTransform;
394
- if (!vt2) return;
399
+ const viewportTransform2 = canvas.viewportTransform;
400
+ if (!viewportTransform2) return;
395
401
  canvas.setViewportTransform([
396
- vt2[0],
397
- vt2[1],
398
- vt2[2],
399
- vt2[3],
402
+ viewportTransform2[0],
403
+ viewportTransform2[1],
404
+ viewportTransform2[2],
405
+ viewportTransform2[3],
400
406
  targetX,
401
407
  targetY
402
408
  ]);
403
409
  return;
404
410
  }
405
411
  const duration = panOpts.duration ?? 300;
406
- const vt = canvas.viewportTransform;
407
- if (!vt) return;
408
- const startX = vt[4];
409
- const startY = vt[5];
412
+ const viewportTransform = canvas.viewportTransform;
413
+ if (!viewportTransform) return;
414
+ const startX = viewportTransform[4];
415
+ const startY = viewportTransform[5];
410
416
  const startTime = performance.now();
411
417
  function step(now) {
412
418
  const elapsed = now - startTime;
@@ -414,13 +420,13 @@ function enablePanAndZoom(canvas, options) {
414
420
  const eased = 1 - Math.pow(1 - t, 3);
415
421
  const currentX = startX + (targetX - startX) * eased;
416
422
  const currentY = startY + (targetY - startY) * eased;
417
- const currentVt = canvas.viewportTransform;
418
- if (!currentVt) return;
423
+ const currentTransform = canvas.viewportTransform;
424
+ if (!currentTransform) return;
419
425
  canvas.setViewportTransform([
420
- currentVt[0],
421
- currentVt[1],
422
- currentVt[2],
423
- currentVt[3],
426
+ currentTransform[0],
427
+ currentTransform[1],
428
+ currentTransform[2],
429
+ currentTransform[3],
424
430
  currentX,
425
431
  currentY
426
432
  ]);
@@ -607,6 +613,7 @@ async function setBackgroundImage(canvas, url, options) {
607
613
  imageUrl = result.url;
608
614
  }
609
615
  const img = await import_fabric4.FabricImage.fromURL(imageUrl, { crossOrigin: "anonymous" });
616
+ img.set({ left: img.width / 2, top: img.height / 2 });
610
617
  canvas.backgroundImage = img;
611
618
  if (prevContrast !== void 0 && prevContrast !== 1) {
612
619
  setBackgroundContrast(canvas, prevContrast);
@@ -800,63 +807,6 @@ registerSnapPointExtractor(
800
807
  // src/alignment/objectAlignment.ts
801
808
  var import_fabric7 = require("fabric");
802
809
 
803
- // src/alignment/objectAlignmentRendering.ts
804
- function drawAlignmentLine(config, origin, target) {
805
- const ctx = config.canvas.getTopContext();
806
- const vt = config.canvas.viewportTransform;
807
- const zoom = config.canvas.getZoom();
808
- ctx.save();
809
- ctx.transform(...vt);
810
- ctx.lineWidth = config.width / zoom;
811
- if (config.lineDash) ctx.setLineDash(config.lineDash);
812
- ctx.strokeStyle = config.color;
813
- ctx.beginPath();
814
- ctx.moveTo(origin.x, origin.y);
815
- ctx.lineTo(target.x, target.y);
816
- ctx.stroke();
817
- if (config.lineDash) ctx.setLineDash([]);
818
- drawXMarker(ctx, origin, config.xSize / zoom);
819
- drawXMarker(ctx, target, config.xSize / zoom);
820
- ctx.restore();
821
- }
822
- function drawXMarker(ctx, point, size) {
823
- ctx.save();
824
- ctx.translate(point.x, point.y);
825
- ctx.beginPath();
826
- ctx.moveTo(-size, -size);
827
- ctx.lineTo(size, size);
828
- ctx.moveTo(size, -size);
829
- ctx.lineTo(-size, size);
830
- ctx.stroke();
831
- ctx.restore();
832
- }
833
- function drawMarkerList(config, lines) {
834
- const ctx = config.canvas.getTopContext();
835
- const vt = config.canvas.viewportTransform;
836
- const zoom = config.canvas.getZoom();
837
- const markerSize = config.xSize / zoom;
838
- ctx.save();
839
- ctx.transform(...vt);
840
- ctx.lineWidth = config.width / zoom;
841
- ctx.strokeStyle = config.color;
842
- for (const item of lines) drawXMarker(ctx, item.target, markerSize);
843
- ctx.restore();
844
- }
845
- function drawVerticalAlignmentLines(config, lines) {
846
- for (const v of lines) {
847
- const { origin, target } = JSON.parse(v);
848
- const from = { x: target.x, y: origin.y };
849
- drawAlignmentLine(config, from, target);
850
- }
851
- }
852
- function drawHorizontalAlignmentLines(config, lines) {
853
- for (const h of lines) {
854
- const { origin, target } = JSON.parse(h);
855
- const from = { x: origin.x, y: target.y };
856
- drawAlignmentLine(config, from, target);
857
- }
858
- }
859
-
860
810
  // src/alignment/objectAlignmentMath.ts
861
811
  var OPPOSITE_ORIGIN_MAP = {
862
812
  tl: ["right", "bottom"],
@@ -980,6 +930,61 @@ function collectHorizontalSnapOffset(props) {
980
930
  }
981
931
 
982
932
  // src/alignment/objectAlignment.ts
933
+ function drawXMarker(ctx, point, size) {
934
+ ctx.save();
935
+ ctx.translate(point.x, point.y);
936
+ ctx.beginPath();
937
+ ctx.moveTo(-size, -size);
938
+ ctx.lineTo(size, size);
939
+ ctx.moveTo(size, -size);
940
+ ctx.lineTo(-size, size);
941
+ ctx.stroke();
942
+ ctx.restore();
943
+ }
944
+ function drawAlignmentLine(config, origin, target) {
945
+ const ctx = config.canvas.getTopContext();
946
+ const vt = config.canvas.viewportTransform;
947
+ const zoom = config.canvas.getZoom();
948
+ ctx.save();
949
+ ctx.transform(...vt);
950
+ ctx.lineWidth = config.width / zoom;
951
+ if (config.lineDash) ctx.setLineDash(config.lineDash);
952
+ ctx.strokeStyle = config.color;
953
+ ctx.beginPath();
954
+ ctx.moveTo(origin.x, origin.y);
955
+ ctx.lineTo(target.x, target.y);
956
+ ctx.stroke();
957
+ if (config.lineDash) ctx.setLineDash([]);
958
+ drawXMarker(ctx, origin, config.xSize / zoom);
959
+ drawXMarker(ctx, target, config.xSize / zoom);
960
+ ctx.restore();
961
+ }
962
+ function drawMarkerList(config, lines) {
963
+ const ctx = config.canvas.getTopContext();
964
+ const vt = config.canvas.viewportTransform;
965
+ const zoom = config.canvas.getZoom();
966
+ const markerSize = config.xSize / zoom;
967
+ ctx.save();
968
+ ctx.transform(...vt);
969
+ ctx.lineWidth = config.width / zoom;
970
+ ctx.strokeStyle = config.color;
971
+ for (const item of lines) drawXMarker(ctx, item.target, markerSize);
972
+ ctx.restore();
973
+ }
974
+ function drawVerticalAlignmentLines(config, lines) {
975
+ for (const v of lines) {
976
+ const { origin, target } = JSON.parse(v);
977
+ const from = { x: target.x, y: origin.y };
978
+ drawAlignmentLine(config, from, target);
979
+ }
980
+ }
981
+ function drawHorizontalAlignmentLines(config, lines) {
982
+ for (const h of lines) {
983
+ const { origin, target } = JSON.parse(h);
984
+ const from = { x: origin.x, y: target.y };
985
+ drawAlignmentLine(config, from, target);
986
+ }
987
+ }
983
988
  function adjustCornerForFlip(corner, target) {
984
989
  let adjusted = corner;
985
990
  if (target.flipX) {
@@ -1034,9 +1039,13 @@ var ObjectAlignmentGuides = class {
1034
1039
  }
1035
1040
  // --- Margin calculation ---
1036
1041
  computeMargin() {
1037
- const zoom = this.canvas.getZoom();
1038
- const sizeScale = this.scaleWithCanvasSize ? Math.max(this.canvas.width ?? 800, this.canvas.height ?? 600) / BASE_CANVAS_SIZE : 1;
1039
- return this.margin * sizeScale / zoom;
1042
+ return computeSnapMargin(
1043
+ this.canvas.width ?? 800,
1044
+ this.canvas.height ?? 600,
1045
+ this.canvas.getZoom(),
1046
+ this.margin,
1047
+ this.scaleWithCanvasSize
1048
+ );
1040
1049
  }
1041
1050
  // --- Snap point caching ---
1042
1051
  getCachedSnapPoints(object) {
@@ -1172,10 +1181,13 @@ function enableRotationSnap(canvas, options) {
1172
1181
  // src/alignment/cursorSnapping.ts
1173
1182
  var import_fabric8 = require("fabric");
1174
1183
  function snapCursorPoint(canvas, rawPoint, options) {
1175
- const zoom = canvas.getZoom();
1176
- const scaleWithSize = options?.scaleWithCanvasSize !== false;
1177
- const sizeScale = scaleWithSize ? Math.max(canvas.width ?? 800, canvas.height ?? 600) / BASE_CANVAS_SIZE : 1;
1178
- const margin = (options?.margin ?? DEFAULT_SNAP_MARGIN) * sizeScale / zoom;
1184
+ const margin = computeSnapMargin(
1185
+ canvas.width ?? 800,
1186
+ canvas.height ?? 600,
1187
+ canvas.getZoom(),
1188
+ options?.margin ?? DEFAULT_SNAP_MARGIN,
1189
+ options?.scaleWithCanvasSize !== false
1190
+ );
1179
1191
  const exclude = options?.exclude ?? /* @__PURE__ */ new Set();
1180
1192
  let targetPoints;
1181
1193
  if (options?.targetPoints) {
@@ -2228,20 +2240,7 @@ function enableScaledBorderRadius(canvas, options) {
2228
2240
  function getBaseStrokeWidth(obj) {
2229
2241
  return strokeBaseMap.get(obj) ?? obj.strokeWidth ?? 0;
2230
2242
  }
2231
- function serializeCanvas(canvas, options) {
2232
- const properties = [
2233
- "data",
2234
- "shapeType",
2235
- // Control styling — absent from Fabric's default toObject output
2236
- "borderColor",
2237
- "cornerColor",
2238
- "cornerStrokeColor",
2239
- "transparentCorners",
2240
- // Interaction locks — absent from Fabric's default toObject output
2241
- "lockRotation",
2242
- "lockUniScaling",
2243
- ...options?.properties ?? []
2244
- ];
2243
+ function prepareStrokeWidths(canvas) {
2245
2244
  const scaledWidths = /* @__PURE__ */ new Map();
2246
2245
  canvas.forEachObject((obj) => {
2247
2246
  const base = strokeBaseMap.get(obj);
@@ -2250,6 +2249,11 @@ function serializeCanvas(canvas, options) {
2250
2249
  obj.strokeWidth = base;
2251
2250
  }
2252
2251
  });
2252
+ return () => scaledWidths.forEach((scaled, obj) => {
2253
+ obj.strokeWidth = scaled;
2254
+ });
2255
+ }
2256
+ function prepareBorderRadii(canvas) {
2253
2257
  const appliedRadii = /* @__PURE__ */ new Map();
2254
2258
  canvas.forEachObject((obj) => {
2255
2259
  if (!(obj instanceof import_fabric16.Rect)) return;
@@ -2259,6 +2263,11 @@ function serializeCanvas(canvas, options) {
2259
2263
  obj.set({ rx: base.rx, ry: base.ry });
2260
2264
  }
2261
2265
  });
2266
+ return () => appliedRadii.forEach((radii, obj) => {
2267
+ obj.set(radii);
2268
+ });
2269
+ }
2270
+ function prepareObjectOrigins(canvas) {
2262
2271
  const savedOrigins = /* @__PURE__ */ new Map();
2263
2272
  canvas.forEachObject((obj) => {
2264
2273
  if (obj.originX === "left" && obj.originY === "top") return;
@@ -2269,20 +2278,36 @@ function serializeCanvas(canvas, options) {
2269
2278
  top: obj.top ?? 0
2270
2279
  });
2271
2280
  const leftTop = obj.getPositionByOrigin("left", "top");
2272
- obj.set({ originX: "left", originY: "top", left: leftTop.x, top: leftTop.y });
2281
+ obj.set({
2282
+ originX: "left",
2283
+ originY: "top",
2284
+ left: leftTop.x,
2285
+ top: leftTop.y
2286
+ });
2273
2287
  });
2288
+ return () => savedOrigins.forEach((saved, obj) => {
2289
+ obj.set(saved);
2290
+ });
2291
+ }
2292
+ function prepareBackgroundOrigin(canvas) {
2274
2293
  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
2294
+ if (!(bg instanceof import_fabric16.FabricImage) || bg.originX === "left" && bg.originY === "top") {
2295
+ return () => {
2282
2296
  };
2283
- const leftTop = bg.getPositionByOrigin("left", "top");
2284
- bg.set({ originX: "left", originY: "top", left: leftTop.x, top: leftTop.y });
2285
2297
  }
2298
+ const saved = {
2299
+ originX: bg.originX,
2300
+ originY: bg.originY,
2301
+ left: bg.left ?? 0,
2302
+ top: bg.top ?? 0
2303
+ };
2304
+ const leftTop = bg.getPositionByOrigin("left", "top");
2305
+ bg.set({ originX: "left", originY: "top", left: leftTop.x, top: leftTop.y });
2306
+ return () => {
2307
+ bg.set(saved);
2308
+ };
2309
+ }
2310
+ function prepareStrokeWidthBaseData(canvas) {
2286
2311
  const savedData = /* @__PURE__ */ new Map();
2287
2312
  canvas.forEachObject((obj) => {
2288
2313
  const base = strokeBaseMap.get(obj) ?? obj.strokeWidth;
@@ -2294,33 +2319,53 @@ function serializeCanvas(canvas, options) {
2294
2319
  };
2295
2320
  }
2296
2321
  });
2322
+ return () => savedData.forEach((originalData, obj) => {
2323
+ obj.data = originalData;
2324
+ });
2325
+ }
2326
+ function serializeCanvas(canvas, options) {
2327
+ const properties = [
2328
+ "data",
2329
+ "shapeType",
2330
+ // Control styling — absent from Fabric's default toObject output
2331
+ "borderColor",
2332
+ "cornerColor",
2333
+ "cornerStrokeColor",
2334
+ "transparentCorners",
2335
+ // Interaction locks — absent from Fabric's default toObject output
2336
+ "lockRotation",
2337
+ "lockUniScaling",
2338
+ ...options?.properties ?? []
2339
+ ];
2340
+ const restoreStrokeWidths = prepareStrokeWidths(canvas);
2341
+ const restoreBorderRadii = prepareBorderRadii(canvas);
2342
+ const restoreOrigins = prepareObjectOrigins(canvas);
2343
+ const restoreBgOrigin = prepareBackgroundOrigin(canvas);
2344
+ const restoreData = prepareStrokeWidthBaseData(canvas);
2297
2345
  const json = canvas.toObject(properties);
2298
2346
  delete json.backgroundColor;
2299
2347
  json.backgroundFilters = {
2300
2348
  opacity: getBackgroundContrast(canvas),
2301
2349
  inverted: getBackgroundInverted(canvas)
2302
2350
  };
2303
- scaledWidths.forEach((scaled, obj) => {
2304
- obj.strokeWidth = scaled;
2305
- });
2306
- appliedRadii.forEach((radii, obj) => {
2307
- obj.set({ rx: radii.rx, ry: radii.ry });
2308
- });
2309
- savedOrigins.forEach((saved, obj) => {
2310
- obj.set(saved);
2311
- });
2312
- if (savedBgOrigin && bg instanceof import_fabric16.FabricImage) {
2313
- bg.set(savedBgOrigin);
2351
+ if (canvas.lockLightMode !== void 0) {
2352
+ json.lockLightMode = canvas.lockLightMode;
2314
2353
  }
2315
- savedData.forEach((originalData, obj) => {
2316
- obj.data = originalData;
2317
- });
2354
+ restoreStrokeWidths();
2355
+ restoreBorderRadii();
2356
+ restoreOrigins();
2357
+ restoreBgOrigin();
2358
+ restoreData();
2318
2359
  return json;
2319
2360
  }
2320
2361
  async function loadCanvas(canvas, json, options) {
2321
2362
  await canvas.loadFromJSON(json);
2322
2363
  canvas.backgroundColor = "";
2323
2364
  delete canvas.backgroundFilters;
2365
+ const rawCanvas = canvas;
2366
+ if (rawCanvas.lockLightMode !== void 0) {
2367
+ canvas.lockLightMode = rawCanvas.lockLightMode;
2368
+ }
2324
2369
  const bg = canvas.backgroundImage;
2325
2370
  if (bg instanceof import_fabric16.FabricImage) {
2326
2371
  if (bg.originX !== "center" || bg.originY !== "center") {
@@ -2513,105 +2558,104 @@ function useEditCanvas(options) {
2513
2558
  (canvas) => {
2514
2559
  canvasRef.current = canvas;
2515
2560
  const opts = optionsRef.current;
2516
- if (opts?.scaledStrokes !== false) {
2517
- enableScaledStrokes(canvas);
2518
- }
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) {
2524
- keyboardCleanupRef.current = enableKeyboardShortcuts(canvas);
2525
- }
2526
- setCanvasAlignmentEnabled(canvas, opts?.enableAlignment);
2527
- if (opts?.panAndZoom !== false) {
2528
- viewportRef.current = enablePanAndZoom(
2529
- canvas,
2530
- typeof opts?.panAndZoom === "object" ? opts.panAndZoom : void 0
2531
- );
2532
- }
2533
- const alignmentEnabled = resolveAlignmentEnabled(
2534
- opts?.enableAlignment,
2535
- opts?.alignment
2536
- );
2537
- if (alignmentEnabled) {
2538
- alignmentCleanupRef.current = enableObjectAlignment(
2539
- canvas,
2540
- typeof opts?.alignment === "object" ? opts.alignment : void 0
2541
- );
2542
- }
2543
- if (opts?.rotationSnap !== false) {
2544
- rotationSnapCleanupRef.current = enableRotationSnap(
2545
- canvas,
2546
- typeof opts?.rotationSnap === "object" ? opts.rotationSnap : void 0
2561
+ setupFeatures();
2562
+ setupEventListeners();
2563
+ invokeOnReady();
2564
+ function setupFeatures() {
2565
+ if (opts?.scaledStrokes !== false) {
2566
+ enableScaledStrokes(canvas);
2567
+ }
2568
+ if (opts?.borderRadius !== false) {
2569
+ const borderRadiusOpts = typeof opts?.borderRadius === "number" ? { radius: opts.borderRadius } : void 0;
2570
+ enableScaledBorderRadius(canvas, borderRadiusOpts);
2571
+ }
2572
+ if (opts?.keyboardShortcuts !== false) {
2573
+ keyboardCleanupRef.current = enableKeyboardShortcuts(canvas);
2574
+ }
2575
+ setCanvasAlignmentEnabled(canvas, opts?.enableAlignment);
2576
+ if (opts?.panAndZoom !== false) {
2577
+ viewportRef.current = enablePanAndZoom(
2578
+ canvas,
2579
+ typeof opts?.panAndZoom === "object" ? opts.panAndZoom : void 0
2580
+ );
2581
+ }
2582
+ const alignmentEnabled = resolveAlignmentEnabled(
2583
+ opts?.enableAlignment,
2584
+ opts?.alignment
2547
2585
  );
2586
+ if (alignmentEnabled) {
2587
+ alignmentCleanupRef.current = enableObjectAlignment(
2588
+ canvas,
2589
+ typeof opts?.alignment === "object" ? opts.alignment : void 0
2590
+ );
2591
+ }
2592
+ if (opts?.rotationSnap !== false) {
2593
+ rotationSnapCleanupRef.current = enableRotationSnap(
2594
+ canvas,
2595
+ typeof opts?.rotationSnap === "object" ? opts.rotationSnap : void 0
2596
+ );
2597
+ }
2598
+ if (opts?.history) {
2599
+ const historyOpts = typeof opts.history === "object" ? opts.history : void 0;
2600
+ historyRef.current = createHistoryTracker(canvas, historyOpts);
2601
+ }
2548
2602
  }
2549
- canvas.on("mouse:wheel", () => {
2550
- setZoom(canvas.getZoom());
2551
- });
2552
- canvas.on("selection:created", (e) => {
2553
- setSelected(e.selected ?? []);
2554
- });
2555
- canvas.on("selection:updated", (e) => {
2556
- setSelected(e.selected ?? []);
2557
- });
2558
- canvas.on("selection:cleared", () => {
2559
- setSelected([]);
2560
- });
2561
- if (opts?.trackChanges) {
2562
- canvas.on("object:added", () => setIsDirty(true));
2563
- canvas.on("object:removed", () => setIsDirty(true));
2564
- canvas.on("object:modified", () => setIsDirty(true));
2565
- }
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;
2581
- canvas.on("mouse:dblclick", (e) => {
2582
- if (e.target && e.target instanceof import_fabric17.Polygon) {
2583
- vertexEditCleanupRef.current?.();
2584
- vertexEditCleanupRef.current = enableVertexEdit(canvas, e.target, {
2585
- ...vertexOpts,
2586
- onExit: () => {
2587
- vertexEditCleanupRef.current = null;
2588
- setIsEditingVertices(false);
2589
- }
2590
- });
2591
- setIsEditingVertices(true);
2592
- }
2593
- });
2594
- }
2595
- if (opts?.history) {
2596
- const historyOpts = typeof opts.history === "object" ? opts.history : void 0;
2597
- historyRef.current = createHistoryTracker(canvas, historyOpts);
2603
+ function setupEventListeners() {
2604
+ canvas.on("mouse:wheel", () => setZoom(canvas.getZoom()));
2605
+ canvas.on("selection:created", (e) => setSelected(e.selected ?? []));
2606
+ canvas.on("selection:updated", (e) => setSelected(e.selected ?? []));
2607
+ canvas.on("selection:cleared", () => setSelected([]));
2608
+ if (opts?.trackChanges) {
2609
+ canvas.on("object:added", () => setIsDirty(true));
2610
+ canvas.on("object:removed", () => setIsDirty(true));
2611
+ canvas.on("object:modified", () => setIsDirty(true));
2612
+ }
2613
+ if (opts?.history) {
2614
+ const syncHistoryState = () => {
2615
+ const h = historyRef.current;
2616
+ if (!h) return;
2617
+ setTimeout(() => {
2618
+ setCanUndo(h.canUndo());
2619
+ setCanRedo(h.canRedo());
2620
+ }, 350);
2621
+ };
2622
+ canvas.on("object:added", syncHistoryState);
2623
+ canvas.on("object:removed", syncHistoryState);
2624
+ canvas.on("object:modified", syncHistoryState);
2625
+ }
2626
+ if (opts?.vertexEdit !== false) {
2627
+ const vertexOpts = typeof opts?.vertexEdit === "object" ? opts.vertexEdit : void 0;
2628
+ canvas.on("mouse:dblclick", (e) => {
2629
+ if (e.target && e.target instanceof import_fabric17.Polygon) {
2630
+ vertexEditCleanupRef.current?.();
2631
+ vertexEditCleanupRef.current = enableVertexEdit(
2632
+ canvas,
2633
+ e.target,
2634
+ {
2635
+ ...vertexOpts,
2636
+ onExit: () => {
2637
+ vertexEditCleanupRef.current = null;
2638
+ setIsEditingVertices(false);
2639
+ }
2640
+ }
2641
+ );
2642
+ setIsEditingVertices(true);
2643
+ }
2644
+ });
2645
+ }
2598
2646
  }
2599
- const onReadyResult = opts?.onReady?.(canvas);
2600
- if (opts?.autoFitToBackground !== false) {
2647
+ function invokeOnReady() {
2648
+ const onReadyResult = opts?.onReady?.(canvas);
2601
2649
  Promise.resolve(onReadyResult).then(() => {
2602
- if (canvas.backgroundImage) {
2650
+ if (opts?.autoFitToBackground !== false && canvas.backgroundImage) {
2603
2651
  fitViewportToBackground(canvas);
2604
2652
  syncZoom(canvasRef, setZoom);
2605
2653
  }
2606
2654
  historyRef.current?.pushSnapshot();
2607
2655
  });
2608
- } else {
2609
- Promise.resolve(onReadyResult).then(() => {
2610
- historyRef.current?.pushSnapshot();
2611
- });
2612
2656
  }
2613
2657
  },
2614
- // onReady and panAndZoom are intentionally excluded — we only initialize once
2658
+ // Dependency array intentionally empty — we only initialize once on mount
2615
2659
  []
2616
2660
  );
2617
2661
  (0, import_react3.useEffect)(() => {
@@ -3001,18 +3045,24 @@ function useCanvasClick(canvasRef, onClick, options) {
3001
3045
  }, [canvasRef]);
3002
3046
  }
3003
3047
 
3004
- // src/hooks/useObjectOverlay.ts
3048
+ // src/overlay/ObjectOverlay.tsx
3005
3049
  var import_react8 = require("react");
3050
+ var import_material = require("@mui/material");
3006
3051
  var import_fabric18 = require("fabric");
3007
- function useObjectOverlay(canvasRef, object, options) {
3008
- const containerRef = (0, import_react8.useRef)(null);
3009
- const optionsRef = (0, import_react8.useRef)(options);
3010
- optionsRef.current = options;
3052
+ var import_jsx_runtime2 = require("react/jsx-runtime");
3053
+ function ObjectOverlay({
3054
+ canvasRef,
3055
+ object,
3056
+ sx,
3057
+ children,
3058
+ ...rest
3059
+ }) {
3060
+ const stackRef = (0, import_react8.useRef)(null);
3011
3061
  (0, import_react8.useEffect)(() => {
3012
3062
  const canvas = canvasRef.current;
3013
3063
  if (!canvas || !object) return;
3014
3064
  function update() {
3015
- const el = containerRef.current;
3065
+ const el = stackRef.current;
3016
3066
  if (!el || !canvas || !object) return;
3017
3067
  const zoom = canvas.getZoom();
3018
3068
  const vt = canvas.viewportTransform;
@@ -3024,32 +3074,11 @@ function useObjectOverlay(canvasRef, object, options) {
3024
3074
  const screenWidth = actualWidth * zoom;
3025
3075
  const screenHeight = actualHeight * zoom;
3026
3076
  const angle = object.angle ?? 0;
3027
- const opts = optionsRef.current;
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;
3039
- const textEls = el.querySelectorAll(opts.textSelector);
3040
- const display = zoom < textMinScale ? "none" : "";
3041
- textEls.forEach((t) => {
3042
- t.style.display = display;
3043
- });
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` : "";
3052
- }
3077
+ el.style.left = `${screenCoords.x - screenWidth / 2}px`;
3078
+ el.style.top = `${screenCoords.y - screenHeight / 2}px`;
3079
+ el.style.width = `${screenWidth}px`;
3080
+ el.style.height = `${screenHeight}px`;
3081
+ el.style.transform = angle !== 0 ? `rotate(${angle}deg)` : "";
3053
3082
  }
3054
3083
  update();
3055
3084
  canvas.on("after:render", update);
@@ -3063,7 +3092,168 @@ function useObjectOverlay(canvasRef, object, options) {
3063
3092
  object.off("rotating", update);
3064
3093
  };
3065
3094
  }, [canvasRef, object]);
3066
- return containerRef;
3095
+ if (!object) return null;
3096
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3097
+ import_material.Stack,
3098
+ {
3099
+ ref: stackRef,
3100
+ sx: {
3101
+ position: "absolute",
3102
+ pointerEvents: "none",
3103
+ alignItems: "center",
3104
+ justifyContent: "center",
3105
+ zIndex: 1,
3106
+ overflow: "hidden",
3107
+ ...sx
3108
+ },
3109
+ ...rest,
3110
+ children
3111
+ }
3112
+ );
3113
+ }
3114
+
3115
+ // src/overlay/OverlayContent.tsx
3116
+ var import_material2 = require("@mui/material");
3117
+ var import_react9 = require("react");
3118
+ var import_jsx_runtime3 = require("react/jsx-runtime");
3119
+ function OverlayContent({
3120
+ children,
3121
+ padding = 4,
3122
+ maxScale = 2,
3123
+ sx,
3124
+ ...rest
3125
+ }) {
3126
+ const outerRef = (0, import_react9.useRef)(null);
3127
+ const innerRef = (0, import_react9.useRef)(null);
3128
+ (0, import_react9.useEffect)(() => {
3129
+ const outer = outerRef.current;
3130
+ const inner = innerRef.current;
3131
+ if (!outer || !inner) return;
3132
+ function fit() {
3133
+ if (!outer || !inner) return;
3134
+ const containerW = outer.clientWidth;
3135
+ const containerH = outer.clientHeight;
3136
+ const natW = inner.scrollWidth;
3137
+ const natH = inner.scrollHeight;
3138
+ if (natW === 0 || natH === 0 || containerW <= 0 || containerH <= 0) {
3139
+ inner.style.transform = "";
3140
+ inner.style.removeProperty("--overlay-scale");
3141
+ return;
3142
+ }
3143
+ const scale = Math.min(
3144
+ containerW / (natW + padding * 2),
3145
+ containerH / (natH + padding * 2),
3146
+ maxScale
3147
+ );
3148
+ inner.style.transform = `scale(${scale})`;
3149
+ inner.style.setProperty("--overlay-scale", String(scale));
3150
+ }
3151
+ const observer = new ResizeObserver(fit);
3152
+ observer.observe(outer);
3153
+ observer.observe(inner);
3154
+ fit();
3155
+ return () => observer.disconnect();
3156
+ }, [padding, maxScale]);
3157
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3158
+ import_material2.Stack,
3159
+ {
3160
+ ref: outerRef,
3161
+ sx: {
3162
+ width: "100%",
3163
+ height: "100%",
3164
+ alignItems: "center",
3165
+ justifyContent: "center",
3166
+ overflow: "hidden",
3167
+ ...sx
3168
+ },
3169
+ ...rest,
3170
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3171
+ import_material2.Stack,
3172
+ {
3173
+ ref: innerRef,
3174
+ sx: {
3175
+ transformOrigin: "center center",
3176
+ flexShrink: 0,
3177
+ width: "max-content"
3178
+ },
3179
+ children
3180
+ }
3181
+ )
3182
+ }
3183
+ );
3184
+ }
3185
+
3186
+ // src/overlay/FixedSizeContent.tsx
3187
+ var import_material3 = require("@mui/material");
3188
+ var import_react10 = require("react");
3189
+ var import_jsx_runtime4 = require("react/jsx-runtime");
3190
+ function FixedSizeContent({
3191
+ children,
3192
+ hideOnOverflow = true,
3193
+ truncationPadding = 4,
3194
+ sx,
3195
+ ...rest
3196
+ }) {
3197
+ const ref = (0, import_react10.useRef)(null);
3198
+ const totalContentHeightRef = (0, import_react10.useRef)(0);
3199
+ (0, import_react10.useEffect)(() => {
3200
+ const el = ref.current;
3201
+ if (!el) return;
3202
+ let clipAncestor = el.parentElement;
3203
+ while (clipAncestor) {
3204
+ if (getComputedStyle(clipAncestor).overflow === "hidden") break;
3205
+ clipAncestor = clipAncestor.parentElement;
3206
+ }
3207
+ if (!clipAncestor) return;
3208
+ const ancestor = clipAncestor;
3209
+ totalContentHeightRef.current = el.parentElement?.scrollHeight ?? 0;
3210
+ function check() {
3211
+ requestAnimationFrame(() => {
3212
+ if (!el) return;
3213
+ const containerRect = ancestor.getBoundingClientRect();
3214
+ el.style.maxWidth = `${Math.max(0, containerRect.width - truncationPadding * 2)}px`;
3215
+ if (!hideOnOverflow) return;
3216
+ const fits = containerRect.height >= totalContentHeightRef.current;
3217
+ if (fits && el.style.display === "none") {
3218
+ el.style.display = "";
3219
+ totalContentHeightRef.current = el.parentElement?.scrollHeight ?? 0;
3220
+ if (containerRect.height < totalContentHeightRef.current) {
3221
+ el.style.display = "none";
3222
+ }
3223
+ } else if (!fits && el.style.display !== "none") {
3224
+ el.style.display = "none";
3225
+ }
3226
+ if (el.style.display !== "none") {
3227
+ totalContentHeightRef.current = el.parentElement?.scrollHeight ?? 0;
3228
+ }
3229
+ });
3230
+ }
3231
+ const observer = new ResizeObserver(check);
3232
+ observer.observe(ancestor);
3233
+ check();
3234
+ return () => observer.disconnect();
3235
+ }, [hideOnOverflow, truncationPadding]);
3236
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3237
+ import_material3.Stack,
3238
+ {
3239
+ ref,
3240
+ sx: {
3241
+ transform: "scale(calc(1 / var(--overlay-scale, 1)))",
3242
+ transformOrigin: "center center",
3243
+ flexShrink: 0,
3244
+ width: "max-content",
3245
+ overflow: "hidden",
3246
+ "& > *": {
3247
+ maxWidth: "100%",
3248
+ overflow: "hidden",
3249
+ textOverflow: "ellipsis"
3250
+ },
3251
+ ...sx
3252
+ },
3253
+ ...rest,
3254
+ children
3255
+ }
3256
+ );
3067
3257
  }
3068
3258
 
3069
3259
  // src/index.ts
@@ -3080,6 +3270,9 @@ var import_fabric19 = require("fabric");
3080
3270
  FabricCanvas,
3081
3271
  FabricImage,
3082
3272
  FabricObject,
3273
+ FixedSizeContent,
3274
+ ObjectOverlay,
3275
+ OverlayContent,
3083
3276
  Point,
3084
3277
  Polygon,
3085
3278
  Rect,
@@ -3125,7 +3318,6 @@ var import_fabric19 = require("fabric");
3125
3318
  useCanvasEvents,
3126
3319
  useCanvasTooltip,
3127
3320
  useEditCanvas,
3128
- useObjectOverlay,
3129
3321
  useViewCanvas,
3130
3322
  util
3131
3323
  });