@leapfrog/interactive-canvas 2.0.25-beta-13 → 2.0.25-beta-15

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.
@@ -253,9 +253,15 @@ class ResizeCanvasLayoutFunction {
253
253
  const targetHeight = increase.withSize(increase.width, baseDimensions.height + increase.height);
254
254
  if (currentDimensions.height !== targetHeight.height) {
255
255
  canvas.setDimensions(targetHeight);
256
+ canvas.getObjectsInternal().forEach((object) => {
257
+ var _a;
258
+ const canvasObj = canvas.getObjectsByTypeInternal(object.type).find(o => o.getId() === object.getId());
259
+ if (canvasObj && (((_a = canvasObj.isPinToBottom) === null || _a === void 0 ? void 0 : _a.call(canvasObj)) || object.top > currentDimensions.heightPx * 0.5)) {
260
+ object.setCoords();
261
+ }
262
+ });
263
+ canvas.redraw();
256
264
  }
257
- canvas.getObjectsInternal().forEach((object) => object.setCoords());
258
- canvas.redraw();
259
265
  }
260
266
  }
261
267
 
@@ -273,38 +279,6 @@ class RepositionBackgroundsLayoutFunction {
273
279
  }
274
280
  }
275
281
 
276
- class AdvancedLayoutManager extends AbstractLayoutManager {
277
- constructor(canvas) {
278
- super(canvas);
279
- this.key = exports.LayoutManager.ADVANCED;
280
- this.pinToBottom = new PinToBottomLayoutFunction();
281
- this.redrawAll = new RedrawAllLayoutFunction();
282
- this.repositionBorders = new RepositionBordersLayoutFunction();
283
- this.repositionCutoffs = new RepositionCutoffsLayoutFunction();
284
- this.repositionBackgrounds = new RepositionBackgroundsLayoutFunction();
285
- this.resizeCanvas = new ResizeCanvasLayoutFunction();
286
- }
287
- onCanvasDimensionsChange() {
288
- this.pinToBottom.execute(this.canvas);
289
- this.repositionCutoffs.execute(this.canvas);
290
- this.repositionBorders.execute(this.canvas);
291
- this.repositionBackgrounds.execute(this.canvas);
292
- this.redrawAll.execute(this.canvas);
293
- }
294
- onTextChange() {
295
- this.resizeCanvas.execute(this.canvas);
296
- }
297
- onObjectListChange() {
298
- this.redrawAll.execute(this.canvas);
299
- }
300
- }
301
-
302
- class UpdateObjectDimensionsLayoutFunction {
303
- execute(canvas) {
304
- canvas.getObjectsInternal().forEach((object) => object.updateDimensions());
305
- }
306
- }
307
-
308
282
  const MIN_MAGNITUDE = 1;
309
283
  class StackObjectsLayoutFunction {
310
284
  execute(canvas) {
@@ -317,7 +291,6 @@ class StackObjectsLayoutFunction {
317
291
  const footerObjects = objects[0];
318
292
  const bodyObjects = objects[1];
319
293
  const bodyWidth = canvasDimensions.widthPx - canvasMargin.right - canvasMargin.left;
320
- canvas.redraw();
321
294
  //Make one pass and set the widths of all elements. This will force objects with dynamic heights (like text) to take on their necessary height before trying to lay them out.
322
295
  this.applyWidths(allObjects, bodyWidth);
323
296
  const bodyRows = this.getRows(bodyObjects);
@@ -404,7 +377,11 @@ class StackObjectsLayoutFunction {
404
377
  object.setPosition({
405
378
  width: actualWidth
406
379
  });
407
- object.updateDimensions();
380
+ });
381
+ _(objects).forEach((object) => {
382
+ if (object.getType() !== exports.ObjectType.VERTICAL_RULE) {
383
+ object.updateDimensions();
384
+ }
408
385
  });
409
386
  }
410
387
  adjustCanvasSize(canvas, requiredHeight) {
@@ -465,6 +442,73 @@ class StackObjectsLayoutFunction {
465
442
  }
466
443
  }
467
444
 
445
+ exports.ObjectType = void 0;
446
+ (function (ObjectType) {
447
+ ObjectType["TEXT"] = "TEXT";
448
+ ObjectType["BORDER"] = "BORDER";
449
+ ObjectType["IMAGE"] = "IMAGE";
450
+ ObjectType["RECTANGLE"] = "RECTANGLE";
451
+ ObjectType["LIBRARY_IMAGE"] = "LIBRARY_IMAGE";
452
+ ObjectType["CUTOFF"] = "CUTOFF";
453
+ ObjectType["VERTICAL_RULE"] = "VERTICAL_RULE";
454
+ ObjectType["HORIZONTAL_RULE"] = "HORIZONTAL_RULE";
455
+ ObjectType["RICH_TEXT"] = "RICH_TEXT";
456
+ ObjectType["BACKGROUND_IMAGE"] = "BACKGROUND_IMAGE";
457
+ })(exports.ObjectType || (exports.ObjectType = {}));
458
+
459
+ class AdvancedLayoutManager extends AbstractLayoutManager {
460
+ constructor(canvas) {
461
+ super(canvas);
462
+ this.key = exports.LayoutManager.ADVANCED;
463
+ this.pinToBottom = new PinToBottomLayoutFunction();
464
+ this.redrawAll = new RedrawAllLayoutFunction();
465
+ this.repositionBorders = new RepositionBordersLayoutFunction();
466
+ this.repositionCutoffs = new RepositionCutoffsLayoutFunction();
467
+ this.repositionBackgrounds = new RepositionBackgroundsLayoutFunction();
468
+ this.resizeCanvas = new ResizeCanvasLayoutFunction();
469
+ this.stackObjects = new StackObjectsLayoutFunction();
470
+ this.dimensionsLayoutScheduled = false;
471
+ }
472
+ onCanvasDimensionsChange() {
473
+ if (this.dimensionsLayoutScheduled) {
474
+ return;
475
+ }
476
+ this.dimensionsLayoutScheduled = true;
477
+ Promise.resolve().then(() => {
478
+ this.pinToBottom.execute(this.canvas);
479
+ this.dimensionChange();
480
+ this.repositionCutoffs.execute(this.canvas);
481
+ this.repositionBorders.execute(this.canvas);
482
+ this.repositionBackgrounds.execute(this.canvas);
483
+ this.redrawAll.execute(this.canvas);
484
+ this.dimensionsLayoutScheduled = false;
485
+ });
486
+ }
487
+ onTextChange() {
488
+ this.resizeCanvas.execute(this.canvas);
489
+ }
490
+ onObjectListChange() {
491
+ this.redrawAll.execute(this.canvas);
492
+ }
493
+ dimensionChange() {
494
+ const canvasMargin = this.canvas.getMargin();
495
+ const canvasDimensions = this.canvas.getDimensions();
496
+ const allObjects = this.canvas.getObjectsByTypesInternal(exports.ObjectType.TEXT, exports.ObjectType.RICH_TEXT,
497
+ //ObjectType.IMAGE,
498
+ exports.ObjectType.RECTANGLE, exports.ObjectType.VERTICAL_RULE, exports.ObjectType.HORIZONTAL_RULE);
499
+ const bodyWidth = canvasDimensions.widthPx - canvasMargin.right - canvasMargin.left;
500
+ this.canvas.redraw();
501
+ //Make one pass and set the widths of all elements. This will force objects with dynamic heights (like text) to take on their necessary height before trying to lay them out.
502
+ this.stackObjects.applyWidths(allObjects, bodyWidth);
503
+ }
504
+ }
505
+
506
+ class UpdateObjectDimensionsLayoutFunction {
507
+ execute(canvas) {
508
+ canvas.getObjectsInternal().forEach((object) => object.updateDimensions());
509
+ }
510
+ }
511
+
468
512
  class StandardLayoutManager extends AbstractLayoutManager {
469
513
  constructor(canvas) {
470
514
  super(canvas);
@@ -477,6 +521,7 @@ class StandardLayoutManager extends AbstractLayoutManager {
477
521
  this.repositionBackgrounds = new RepositionBackgroundsLayoutFunction();
478
522
  this.stackObjects = new StackObjectsLayoutFunction();
479
523
  this.updateObjectDimensions = new UpdateObjectDimensionsLayoutFunction();
524
+ this.layoutBatchScheduled = false;
480
525
  }
481
526
  /**
482
527
  * @override
@@ -518,12 +563,19 @@ class StandardLayoutManager extends AbstractLayoutManager {
518
563
  this.stackAndRedraw();
519
564
  }
520
565
  stackAndRedraw() {
521
- this.stackObjects.execute(this.canvas);
522
- this.repositionCutoffs.execute(this.canvas);
523
- this.repositionBorders.execute(this.canvas);
524
- this.repositionBackgrounds.execute(this.canvas);
525
- this.updateObjectDimensions.execute(this.canvas);
526
- this.redrawAll.execute(this.canvas);
566
+ if (this.layoutBatchScheduled) {
567
+ return;
568
+ }
569
+ this.layoutBatchScheduled = true;
570
+ Promise.resolve().then(() => {
571
+ this.stackObjects.execute(this.canvas);
572
+ this.repositionCutoffs.execute(this.canvas);
573
+ this.repositionBorders.execute(this.canvas);
574
+ this.repositionBackgrounds.execute(this.canvas);
575
+ this.updateObjectDimensions.execute(this.canvas);
576
+ this.redrawAll.execute(this.canvas);
577
+ this.layoutBatchScheduled = false;
578
+ });
527
579
  }
528
580
  }
529
581
 
@@ -990,7 +1042,7 @@ class AbstractCanvasObject {
990
1042
  //no-op
991
1043
  }
992
1044
  preventLeavingCanvas() {
993
- const overflow = this.getCanvasOverflow();
1045
+ const overflow = this.getCanvasOverflowForBoundaryPrevention();
994
1046
  if (overflow.hasOverflow) {
995
1047
  const adjustX = overflow.left || -overflow.right;
996
1048
  const adjustY = overflow.top || -overflow.bottom;
@@ -1016,6 +1068,29 @@ class AbstractCanvasObject {
1016
1068
  hasOverflow: top > 0 || right > 0 || bottom > 0 || left > 0
1017
1069
  };
1018
1070
  }
1071
+ getCanvasOverflowForBoundaryPrevention() {
1072
+ // Use a simpler bounding box calculation for boundary prevention
1073
+ // to avoid issues with decorative elements (like ordered list markers)
1074
+ // extending beyond the main object bounds
1075
+ const bound = this.getSimpleBoundingBox();
1076
+ const canvasDimensions = this.canvas.getDimensions();
1077
+ const top = Math.max(0, 0 - bound.top);
1078
+ const right = Math.max(0, bound.left + bound.width - canvasDimensions.widthPx);
1079
+ const bottom = Math.max(0, bound.top + bound.height - canvasDimensions.heightPx);
1080
+ const left = Math.max(0, 0 - bound.left);
1081
+ return {
1082
+ top: top,
1083
+ right: right,
1084
+ bottom: bottom,
1085
+ left: left,
1086
+ hasOverflow: top > 0 || right > 0 || bottom > 0 || left > 0
1087
+ };
1088
+ }
1089
+ getSimpleBoundingBox() {
1090
+ // Default implementation: use normal bounding box
1091
+ // Subclasses can override this to provide simpler bounds for drag boundary checks
1092
+ return this.getBoundingBox();
1093
+ }
1019
1094
  centerHorizontallyInternal() {
1020
1095
  const canvasWidth = this.canvas.getDimensions().widthPx;
1021
1096
  const objectWidth = this.getDimensions().width.px;
@@ -1063,7 +1138,8 @@ class AbstractCanvasObject {
1063
1138
  mt: options.allowScale,
1064
1139
  mr: options.allowScale,
1065
1140
  mb: options.allowScale,
1066
- mtr: options.allowRotate
1141
+ mtr: options.allowRotate,
1142
+ // dragControl: options.allowMove // Show drag control when movement is allowed
1067
1143
  });
1068
1144
  }
1069
1145
  }
@@ -1784,32 +1860,32 @@ class RichText extends Image {
1784
1860
  }
1785
1861
  }
1786
1862
 
1787
- exports.ObjectType = void 0;
1788
- (function (ObjectType) {
1789
- ObjectType["TEXT"] = "TEXT";
1790
- ObjectType["BORDER"] = "BORDER";
1791
- ObjectType["IMAGE"] = "IMAGE";
1792
- ObjectType["RECTANGLE"] = "RECTANGLE";
1793
- ObjectType["LIBRARY_IMAGE"] = "LIBRARY_IMAGE";
1794
- ObjectType["CUTOFF"] = "CUTOFF";
1795
- ObjectType["VERTICAL_RULE"] = "VERTICAL_RULE";
1796
- ObjectType["HORIZONTAL_RULE"] = "HORIZONTAL_RULE";
1797
- ObjectType["RICH_TEXT"] = "RICH_TEXT";
1798
- ObjectType["BACKGROUND_IMAGE"] = "BACKGROUND_IMAGE";
1799
- })(exports.ObjectType || (exports.ObjectType = {}));
1800
-
1801
1863
  class Text extends AbstractCanvasObject {
1802
1864
  constructor(canvas, fabricObject, persistedField) {
1803
1865
  super(exports.ObjectType.TEXT, canvas, fabricObject, persistedField);
1866
+ this.notifyCanvasTimeout = null;
1867
+ this.NOTIFY_DELAY_MS = 150; // Debounce canvas notifications
1804
1868
  this.canvas = canvas;
1805
1869
  this.fontLibrary = canvas.getFontLibrary();
1806
1870
  this.baseComponentHeight = this.fabricObject.height;
1807
1871
  this.text$ = new rxjs.BehaviorSubject(this.fabricObject.text);
1808
1872
  this.data.userText = this.fabricObject.text;
1809
1873
  this.textSelection$ = new rxjs.BehaviorSubject(new TextSelection(this.fabricObject, this.fontLibrary));
1810
- this.fabricObject.set({ strokeWidth: 0 });
1874
+ this.fabricObject.set({
1875
+ strokeWidth: 0,
1876
+ perPixelTargetFind: false, // Disable per-pixel detection for this object
1877
+ padding: 0 // Add padding for larger hover area (Fabric v7 compatibility)
1878
+ });
1811
1879
  this.fabricObject.on("changed", () => this.onTextChange());
1812
1880
  this.fabricObject.on("selection:changed", () => this.onSelectionChange());
1881
+ // Note: Removed containsPoint override to allow Fabric's default bounding box
1882
+ // detection with padding to work properly for hover detection
1883
+ this.fabricObject.on("mousedown", (e) => {
1884
+ var _a;
1885
+ if (!this.fabricObject.isEditing) {
1886
+ (_a = e.e) === null || _a === void 0 ? void 0 : _a.preventDefault();
1887
+ }
1888
+ });
1813
1889
  this.applyBulletListMonkeyPatches();
1814
1890
  }
1815
1891
  /**
@@ -2161,11 +2237,25 @@ class Text extends AbstractCanvasObject {
2161
2237
  getBulletMargin() {
2162
2238
  if (!this.isBulleted())
2163
2239
  return 0;
2164
- let fontSize = 0;
2165
- _.each(this.fabricObject._textLines, (textLine, lineIndex) => {
2166
- fontSize = Math.max(fontSize, this.fabricObject.getValueOfPropertyAt(lineIndex, 0, "fontSize"));
2167
- });
2168
- return fontSize * 0.6;
2240
+ try {
2241
+ let fontSize = 0;
2242
+ _.each(this.fabricObject._textLines, (textLine, lineIndex) => {
2243
+ const size = this.fabricObject.getValueOfPropertyAt(lineIndex, 0, "fontSize");
2244
+ // Sanity check fontSize
2245
+ if (isFinite(size) && size > 0 && size < 1000) {
2246
+ fontSize = Math.max(fontSize, size);
2247
+ }
2248
+ });
2249
+ // Additional safety check
2250
+ if (!isFinite(fontSize) || fontSize < 0 || fontSize > 1000) {
2251
+ return 0;
2252
+ }
2253
+ return fontSize * 0.6;
2254
+ }
2255
+ catch (e) {
2256
+ console.warn('Error calculating bullet margin:', e);
2257
+ return 0;
2258
+ }
2169
2259
  }
2170
2260
  /**
2171
2261
  * @override
@@ -2175,10 +2265,35 @@ class Text extends AbstractCanvasObject {
2175
2265
  if (!this.isOrderedList())
2176
2266
  return 0;
2177
2267
  const orderedListMarkerEntities = this.fabricObject._orderedListMarkerEntities;
2178
- let maxValue = _.max((orderedListMarkerEntities !== null && orderedListMarkerEntities !== void 0 ? orderedListMarkerEntities : [])
2179
- .filter((x) => !!x)
2180
- .map((x) => x.getBoundingRect().width));
2181
- return maxValue ? (maxValue * 1.2) : 0;
2268
+ try {
2269
+ const widths = (orderedListMarkerEntities !== null && orderedListMarkerEntities !== void 0 ? orderedListMarkerEntities : [])
2270
+ .filter((x) => !!x)
2271
+ .map((x) => {
2272
+ var _a;
2273
+ try {
2274
+ const rect = x.getBoundingRect();
2275
+ const width = (_a = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _a !== void 0 ? _a : 0;
2276
+ // Sanity check: if width is invalid, return 0
2277
+ if (!isFinite(width) || width < 0 || width > 1000) {
2278
+ return 0;
2279
+ }
2280
+ return width;
2281
+ }
2282
+ catch (e) {
2283
+ return 0;
2284
+ }
2285
+ });
2286
+ const maxValue = _.max(widths);
2287
+ // Additional safety: ensure maxValue is a valid number
2288
+ if (!maxValue || !isFinite(maxValue) || maxValue < 0 || maxValue > 1000) {
2289
+ return 0;
2290
+ }
2291
+ return maxValue * 1.2;
2292
+ }
2293
+ catch (e) {
2294
+ console.warn('Error calculating ordered list margin:', e);
2295
+ return 0;
2296
+ }
2182
2297
  }
2183
2298
  /**
2184
2299
  * @override
@@ -2419,6 +2534,7 @@ class Text extends AbstractCanvasObject {
2419
2534
  lockMovementY: true,
2420
2535
  hasControls: false,
2421
2536
  selectable: false,
2537
+ // evented: false, // Don't capture mouse events - allows clicks to pass through to textbox
2422
2538
  originX: "left",
2423
2539
  originY: "top"
2424
2540
  });
@@ -2513,7 +2629,7 @@ class Text extends AbstractCanvasObject {
2513
2629
  text: `${lineNumber}.`
2514
2630
  });
2515
2631
  }
2516
- if (method === "fillText" && this.fabricObject.left <= 3) {
2632
+ if (method === "fillText" && this.isOrderedList() && this.fabricObject.left <= 3) {
2517
2633
  left = left + this.getOrderedListMargin();
2518
2634
  }
2519
2635
  this.fabricObject.dirty = true;
@@ -2525,13 +2641,24 @@ class Text extends AbstractCanvasObject {
2525
2641
  * @inheritDoc
2526
2642
  */
2527
2643
  updateFabricObjectBehavior() {
2644
+ // DON'T call super.updateFabricObjectBehavior() for textboxes
2645
+ // because it locks movement which interferes with text editing in Fabric v7
2528
2646
  super.updateFabricObjectBehavior();
2529
2647
  const profile = this.canvas.getProfile();
2530
2648
  const allowManualPositioning = !this.canvas.getLayoutManager().isEnforcedPositioning;
2649
+ // For textboxes, we keep them moveable but hide all controls except drag control
2650
+ // This allows text editing to work properly while still supporting drag via the icon
2531
2651
  this.fabricObject.set({
2652
+ lockMovementX: false, // Allow dragging
2653
+ lockMovementY: false, // Allow dragging
2532
2654
  lockScalingY: true,
2533
2655
  lockScalingX: this.isPinToBottom(),
2534
- lockScalingFlip: true
2656
+ lockScalingFlip: true,
2657
+ lockRotation: true,
2658
+ selectable: true,
2659
+ editable: false, // Only enable on double-click
2660
+ hoverCursor: "move", // Show move cursor by default
2661
+ moveCursor: "move",
2535
2662
  });
2536
2663
  this.fabricObject.setControlsVisibility({
2537
2664
  tl: false,
@@ -2541,35 +2668,64 @@ class Text extends AbstractCanvasObject {
2541
2668
  ml: allowManualPositioning && profile.allowScale && !this.isPinToBottom(),
2542
2669
  mt: false,
2543
2670
  mr: allowManualPositioning && profile.allowScale && !this.isPinToBottom(),
2544
- mb: false
2671
+ mb: false,
2672
+ mtr: false,
2545
2673
  });
2546
2674
  this.redraw();
2547
2675
  }
2548
2676
  onTextChange() {
2549
2677
  this.data.userText = this.fabricObject.text;
2550
2678
  this.text$.next(this.fabricObject.text);
2551
- if (this.getBoundingBox().width > this.canvas.getDimensions().widthPx) {
2552
- this.fabricObject.set({ left: 0, width: this.canvas.getDimensions().widthPx });
2553
- this.redraw();
2679
+ // Only check width bounds if text is close to canvas edge (performance optimization)
2680
+ const currentLeft = this.fabricObject.left || 0;
2681
+ const canvasWidth = this.canvas.getDimensions().widthPx;
2682
+ // Skip expensive getBoundingBox if object is clearly within bounds
2683
+ if (currentLeft + (this.fabricObject.width || 0) > canvasWidth * 0.9) {
2684
+ if (this.getBoundingBox().width > canvasWidth) {
2685
+ this.fabricObject.set({ left: 0, width: canvasWidth });
2686
+ this.redraw();
2687
+ }
2554
2688
  }
2555
2689
  }
2556
2690
  onDelete() {
2557
2691
  this.removeBullets();
2558
2692
  }
2559
2693
  onSelectionChange() {
2560
- this.updateTextSelection();
2694
+ this.updateTextSelection(false);
2561
2695
  }
2562
2696
  hasTextSelection() {
2563
2697
  return this.canvas.getSelection().isText && this.textSelection$.value.length > 0;
2564
2698
  }
2565
- updateTextSelection() {
2699
+ updateTextSelection(notifyCanvas = true) {
2566
2700
  if (this.canvas.headless)
2567
2701
  return;
2568
2702
  this.textSelection$.next(new TextSelection(this.fabricObject, this.fontLibrary));
2569
- this.notifyCanvasOfTextChanges();
2703
+ if (notifyCanvas) {
2704
+ this.notifyCanvasOfTextChanges();
2705
+ }
2570
2706
  }
2571
2707
  notifyCanvasOfTextChanges() {
2572
- this.canvas.notifyTextChanged();
2708
+ if (this.notifyCanvasTimeout) {
2709
+ clearTimeout(this.notifyCanvasTimeout);
2710
+ }
2711
+ this.notifyCanvasTimeout = setTimeout(() => {
2712
+ this.canvas.notifyTextChanged();
2713
+ this.notifyCanvasTimeout = null;
2714
+ }, this.NOTIFY_DELAY_MS);
2715
+ }
2716
+ /**
2717
+ * @override
2718
+ * Provide a simpler bounding box for drag boundary checks that uses the textbox
2719
+ * dimensions directly instead of getBoundingRect(), which includes ordered list
2720
+ * markers that may extend far beyond the text and cause drag shaking issues.
2721
+ */
2722
+ getSimpleBoundingBox() {
2723
+ return {
2724
+ top: this.fabricObject.top || 0,
2725
+ left: this.fabricObject.left || 0,
2726
+ width: (this.fabricObject.width || 0) * (this.fabricObject.scaleX || 1),
2727
+ height: (this.fabricObject.height || 0) * (this.fabricObject.scaleY || 1)
2728
+ };
2573
2729
  }
2574
2730
  }
2575
2731
  Text.NEW_LINE = "\n";
@@ -2735,6 +2891,7 @@ class AbstractInteractiveCanvas {
2735
2891
  this.devicePixelRatio = devicePixelRatio;
2736
2892
  this.fontLibrary = new FontLibrary();
2737
2893
  this.dimensionChangeRejections$ = new rxjs.Subject();
2894
+ this.redrawScheduled = false;
2738
2895
  this.IMAGE_CAPTURE_COEFFICIENT = this.calculateImageCaptureCoefficient();
2739
2896
  /**
2740
2897
  * Fabric defaults to only 2 decimal places when saving, which causes havoc on the scaling of large images.
@@ -2756,8 +2913,8 @@ class AbstractInteractiveCanvas {
2756
2913
  //Configure canvas behaviour
2757
2914
  this.fabricCanvas.stateful = true;
2758
2915
  this.fabricCanvas.preserveObjectStacking = true;
2759
- this.fabricCanvas.perPixelTargetFind = true;
2760
- this.fabricCanvas.targetFindTolerance = 4;
2916
+ this.fabricCanvas.perPixelTargetFind = false; // Disabled for better hover tolerance in Fabric v7
2917
+ this.fabricCanvas.targetFindTolerance = 0; // Increased for better hover detection in Fabric v7 (was 4)
2761
2918
  this.setBackgroundColor("#FFFFFF");
2762
2919
  }
2763
2920
  /**
@@ -3061,7 +3218,6 @@ class AbstractInteractiveCanvas {
3061
3218
  */
3062
3219
  addImageObject(imageUrl) {
3063
3220
  return __awaiter(this, void 0, void 0, function* () {
3064
- // Don't let the image be more than half the canvas in size.
3065
3221
  const maxSizeScale = 2;
3066
3222
  const maxWidth = this.dimensions$.value.widthPx / maxSizeScale;
3067
3223
  const options = { crossOrigin: "anonymous" };
@@ -3073,7 +3229,6 @@ class AbstractInteractiveCanvas {
3073
3229
  if (curWidth > maxWidth) {
3074
3230
  newScale = maxWidth / curWidth;
3075
3231
  }
3076
- // Set image properties and add to the canvas
3077
3232
  const fabricImage = new this.fabric.Image(image, {
3078
3233
  id: id,
3079
3234
  scaleX: newScale,
@@ -3093,7 +3248,6 @@ class AbstractInteractiveCanvas {
3093
3248
  return object;
3094
3249
  }
3095
3250
  else {
3096
- // Browser-side logic
3097
3251
  return yield new Promise((resolve) => {
3098
3252
  this.fabric.Image.fromURL(imageUrl, options).then((fabricImage) => {
3099
3253
  const id = this.randomId();
@@ -3145,15 +3299,14 @@ class AbstractInteractiveCanvas {
3145
3299
  fontFamily: profile.defaultFont,
3146
3300
  textAlign: profile.defaultTextAlignment,
3147
3301
  hoverCursor: "move",
3148
- moveCursor: 'move',
3302
+ moveCursor: "move",
3303
+ editable: false,
3304
+ editingBorderColor: '#2196F3',
3149
3305
  multiLine: true,
3150
3306
  lineHeight: profile.defaultLineHeight,
3151
3307
  charSpacing: profile.defaultCharacterSpacing,
3152
- perPixelTargetFind: false,
3153
- targetFindTolerance: 4,
3154
3308
  splitByGrapheme: true,
3155
- editable: false, // Only enable on double-click
3156
- padding: 0,
3309
+ padding: 0, // Increased from 0 for better hover detection in Fabric v7
3157
3310
  cornerSize: 8,
3158
3311
  touchCornerSize: 12
3159
3312
  };
@@ -3602,9 +3755,17 @@ class AbstractInteractiveCanvas {
3602
3755
  }
3603
3756
  /**
3604
3757
  * Redraw the canvas.
3758
+ * Uses requestAnimationFrame to batch multiple redraw calls into a single render.
3605
3759
  */
3606
3760
  redraw() {
3607
- this.fabricCanvas.requestRenderAll();
3761
+ if (this.redrawScheduled) {
3762
+ return;
3763
+ }
3764
+ this.redrawScheduled = true;
3765
+ requestAnimationFrame(() => {
3766
+ this.fabricCanvas.requestRenderAll();
3767
+ this.redrawScheduled = false;
3768
+ });
3608
3769
  }
3609
3770
  syncObjectList() {
3610
3771
  const newObjectList = _(this.fabricCanvas.getObjects())
@@ -3730,7 +3891,6 @@ class AbstractInteractiveCanvas {
3730
3891
  * Update the actual dimensions of the canvas by multiplying the requested dimensions by the current zoom factor.
3731
3892
  */
3732
3893
  updateCanvasDimensions(dims, zoom) {
3733
- this.fabricCanvas.getWidth();
3734
3894
  const width = dims.widthPx * zoom;
3735
3895
  const height = dims.heightPx * zoom;
3736
3896
  this.fabricCanvas.setZoom(zoom);
@@ -3738,37 +3898,6 @@ class AbstractInteractiveCanvas {
3738
3898
  width,
3739
3899
  height
3740
3900
  });
3741
- /* if (this.layoutManager$.value instanceof AdvancedLayoutManager) {
3742
- const scaleFactor = width / oldWidth;
3743
- this.fabricCanvas.calcOffset();
3744
- const objects = this.fabricCanvas.getObjects();
3745
- objects.forEach((obj: any) => {
3746
- obj.set({
3747
- left: obj.left * scaleFactor,
3748
- });
3749
- if (obj.type === "textbox" || obj.type === "text") {
3750
- obj.set({
3751
- scaleX: 1,
3752
- scaleY: 1,
3753
- width: obj.width * scaleFactor,
3754
- });
3755
- obj.initDimensions();
3756
- } else if (obj.type === "border" || obj.type === "rect") {
3757
- obj.set({
3758
- width: obj.width * scaleFactor,
3759
- scaleX: 1,
3760
- strokeUniform: true,
3761
- strokeAlign: "inner",
3762
- });
3763
- } else {
3764
- obj.set({
3765
- scaleX: obj.scaleX * scaleFactor,
3766
- });
3767
- }
3768
- obj.setCoords();
3769
- });
3770
- this.fabricCanvas.requestRenderAll();
3771
- }*/
3772
3901
  }
3773
3902
  onProfileChanged(profile) {
3774
3903
  this.setMultiSelect(profile.allowMultiSelect);
@@ -3923,7 +4052,6 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
3923
4052
  this.fabricCanvas.on("selection:created", (event) => this.onFabricSelectionChanged(event));
3924
4053
  this.fabricCanvas.on("selection:cleared", (event) => this.onFabricSelectionChanged(event));
3925
4054
  this.fabricCanvas.on("text:editing:entered", () => this.onTextEditing());
3926
- this.fabricCanvas.on("text:editing:exited", () => this.onTextEditing());
3927
4055
  this.fabricCanvas.on("text:editing:exited", (event) => {
3928
4056
  this.onTextEditing();
3929
4057
  const target = event.target;
@@ -3932,8 +4060,6 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
3932
4060
  }
3933
4061
  });
3934
4062
  this.fabricCanvas.on("text:changed", () => this.onTextChange());
3935
- // Handle double-click on textbox to enter editing mode
3936
- // @ts-ignore
3937
4063
  this.fabricCanvas.on("mouse:dblclick", (event) => this.onDoubleClick(event));
3938
4064
  }
3939
4065
  /**
@@ -3968,29 +4094,20 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
3968
4094
  selection.setEvent(event);
3969
4095
  }
3970
4096
  this.selection$.next(selection);
3971
- this.enterTextSelectionModeIfRequired();
3972
- }
3973
- enterTextSelectionModeIfRequired() {
3974
- var _a, _b;
3975
- if (!this.profile$.value.enterTextEditImmediatelyOnSelect && !this.layoutManager$.value.enterTextEditImmediatelyOnSelect)
3976
- return;
3977
- if (((_b = (_a = this.selection$.value) === null || _a === void 0 ? void 0 : _a.objects[0]) === null || _b === void 0 ? void 0 : _b.getType()) !== exports.ObjectType.TEXT)
3978
- return;
3979
- const text = this.selection$.value.objects[0];
3980
- text.enterEditing();
3981
4097
  }
3982
4098
  onTextEditing() {
3983
4099
  const selected = this.getFieldsByCanvasObject(this.fabricCanvas.getActiveObjects());
3984
4100
  this.selection$.next(new SelectionData(selected));
3985
4101
  }
3986
4102
  onDoubleClick(event) {
4103
+ var _a, _b;
3987
4104
  const target = event.target;
3988
4105
  if (target && (target.type === 'textbox' || target.type === 'i-text')) {
3989
- if (!target.isEditing) {
4106
+ const targetIText = target;
4107
+ if (!targetIText.isEditing) {
3990
4108
  target.set({ editable: true, hoverCursor: 'text' });
3991
- // target.enterEditing();
3992
- // target.hiddenTextarea?.focus();
3993
- this.enterTextSelectionModeIfRequired();
4109
+ (_a = targetIText.enterEditing) === null || _a === void 0 ? void 0 : _a.call(targetIText);
4110
+ (_b = targetIText.hiddenTextarea) === null || _b === void 0 ? void 0 : _b.focus();
3994
4111
  }
3995
4112
  }
3996
4113
  }
@@ -9,8 +9,11 @@ export declare class AdvancedLayoutManager extends AbstractLayoutManager {
9
9
  private repositionCutoffs;
10
10
  private repositionBackgrounds;
11
11
  private resizeCanvas;
12
+ private stackObjects;
13
+ private dimensionsLayoutScheduled;
12
14
  constructor(canvas: IInteractiveCanvas);
13
15
  onCanvasDimensionsChange(): void;
14
16
  onTextChange(): void;
15
17
  onObjectListChange(): void;
18
+ private dimensionChange;
16
19
  }
@@ -1,9 +1,10 @@
1
+ import { AbstractCanvasObject } from "../..";
1
2
  import { IInteractiveCanvas } from "../../interactive-canvas.interface";
2
3
  export declare class StackObjectsLayoutFunction {
3
4
  execute(canvas: IInteractiveCanvas): void;
4
5
  private layoutFooter;
5
6
  private layoutBody;
6
- private applyWidths;
7
+ applyWidths(objects: Array<AbstractCanvasObject<any>>, layoutWidth: number): void;
7
8
  private adjustCanvasSize;
8
9
  private getRows;
9
10
  private getObjectHeight;
@@ -36,5 +36,6 @@ export declare class StandardLayoutManager extends AbstractLayoutManager {
36
36
  * @override
37
37
  */
38
38
  onMarginChange(): void;
39
+ private layoutBatchScheduled;
39
40
  private stackAndRedraw;
40
41
  }