@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.
@@ -340,8 +340,10 @@ export declare abstract class AbstractInteractiveCanvas implements IInteractiveC
340
340
  * @inheritDoc
341
341
  */
342
342
  cloneObject(sourceObject: FabricObject, objectData: CanvasObjectData): Promise<void>;
343
+ private redrawScheduled;
343
344
  /**
344
345
  * Redraw the canvas.
346
+ * Uses requestAnimationFrame to batch multiple redraw calls into a single render.
345
347
  */
346
348
  redraw(): void;
347
349
  syncObjectList(): void;
@@ -20,7 +20,6 @@ export declare class InteractiveCanvas extends AbstractInteractiveCanvas {
20
20
  */
21
21
  redo(): Promise<void>;
22
22
  private onFabricSelectionChanged;
23
- private enterTextSelectionModeIfRequired;
24
23
  private onTextEditing;
25
24
  private onDoubleClick;
26
25
  }
@@ -232,9 +232,15 @@ class ResizeCanvasLayoutFunction {
232
232
  const targetHeight = increase.withSize(increase.width, baseDimensions.height + increase.height);
233
233
  if (currentDimensions.height !== targetHeight.height) {
234
234
  canvas.setDimensions(targetHeight);
235
+ canvas.getObjectsInternal().forEach((object) => {
236
+ var _a;
237
+ const canvasObj = canvas.getObjectsByTypeInternal(object.type).find(o => o.getId() === object.getId());
238
+ if (canvasObj && (((_a = canvasObj.isPinToBottom) === null || _a === void 0 ? void 0 : _a.call(canvasObj)) || object.top > currentDimensions.heightPx * 0.5)) {
239
+ object.setCoords();
240
+ }
241
+ });
242
+ canvas.redraw();
235
243
  }
236
- canvas.getObjectsInternal().forEach((object) => object.setCoords());
237
- canvas.redraw();
238
244
  }
239
245
  }
240
246
 
@@ -252,38 +258,6 @@ class RepositionBackgroundsLayoutFunction {
252
258
  }
253
259
  }
254
260
 
255
- class AdvancedLayoutManager extends AbstractLayoutManager {
256
- constructor(canvas) {
257
- super(canvas);
258
- this.key = LayoutManager.ADVANCED;
259
- this.pinToBottom = new PinToBottomLayoutFunction();
260
- this.redrawAll = new RedrawAllLayoutFunction();
261
- this.repositionBorders = new RepositionBordersLayoutFunction();
262
- this.repositionCutoffs = new RepositionCutoffsLayoutFunction();
263
- this.repositionBackgrounds = new RepositionBackgroundsLayoutFunction();
264
- this.resizeCanvas = new ResizeCanvasLayoutFunction();
265
- }
266
- onCanvasDimensionsChange() {
267
- this.pinToBottom.execute(this.canvas);
268
- this.repositionCutoffs.execute(this.canvas);
269
- this.repositionBorders.execute(this.canvas);
270
- this.repositionBackgrounds.execute(this.canvas);
271
- this.redrawAll.execute(this.canvas);
272
- }
273
- onTextChange() {
274
- this.resizeCanvas.execute(this.canvas);
275
- }
276
- onObjectListChange() {
277
- this.redrawAll.execute(this.canvas);
278
- }
279
- }
280
-
281
- class UpdateObjectDimensionsLayoutFunction {
282
- execute(canvas) {
283
- canvas.getObjectsInternal().forEach((object) => object.updateDimensions());
284
- }
285
- }
286
-
287
261
  const MIN_MAGNITUDE = 1;
288
262
  class StackObjectsLayoutFunction {
289
263
  execute(canvas) {
@@ -296,7 +270,6 @@ class StackObjectsLayoutFunction {
296
270
  const footerObjects = objects[0];
297
271
  const bodyObjects = objects[1];
298
272
  const bodyWidth = canvasDimensions.widthPx - canvasMargin.right - canvasMargin.left;
299
- canvas.redraw();
300
273
  //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.
301
274
  this.applyWidths(allObjects, bodyWidth);
302
275
  const bodyRows = this.getRows(bodyObjects);
@@ -383,7 +356,11 @@ class StackObjectsLayoutFunction {
383
356
  object.setPosition({
384
357
  width: actualWidth
385
358
  });
386
- object.updateDimensions();
359
+ });
360
+ _(objects).forEach((object) => {
361
+ if (object.getType() !== ObjectType.VERTICAL_RULE) {
362
+ object.updateDimensions();
363
+ }
387
364
  });
388
365
  }
389
366
  adjustCanvasSize(canvas, requiredHeight) {
@@ -444,6 +421,73 @@ class StackObjectsLayoutFunction {
444
421
  }
445
422
  }
446
423
 
424
+ var ObjectType;
425
+ (function (ObjectType) {
426
+ ObjectType["TEXT"] = "TEXT";
427
+ ObjectType["BORDER"] = "BORDER";
428
+ ObjectType["IMAGE"] = "IMAGE";
429
+ ObjectType["RECTANGLE"] = "RECTANGLE";
430
+ ObjectType["LIBRARY_IMAGE"] = "LIBRARY_IMAGE";
431
+ ObjectType["CUTOFF"] = "CUTOFF";
432
+ ObjectType["VERTICAL_RULE"] = "VERTICAL_RULE";
433
+ ObjectType["HORIZONTAL_RULE"] = "HORIZONTAL_RULE";
434
+ ObjectType["RICH_TEXT"] = "RICH_TEXT";
435
+ ObjectType["BACKGROUND_IMAGE"] = "BACKGROUND_IMAGE";
436
+ })(ObjectType || (ObjectType = {}));
437
+
438
+ class AdvancedLayoutManager extends AbstractLayoutManager {
439
+ constructor(canvas) {
440
+ super(canvas);
441
+ this.key = LayoutManager.ADVANCED;
442
+ this.pinToBottom = new PinToBottomLayoutFunction();
443
+ this.redrawAll = new RedrawAllLayoutFunction();
444
+ this.repositionBorders = new RepositionBordersLayoutFunction();
445
+ this.repositionCutoffs = new RepositionCutoffsLayoutFunction();
446
+ this.repositionBackgrounds = new RepositionBackgroundsLayoutFunction();
447
+ this.resizeCanvas = new ResizeCanvasLayoutFunction();
448
+ this.stackObjects = new StackObjectsLayoutFunction();
449
+ this.dimensionsLayoutScheduled = false;
450
+ }
451
+ onCanvasDimensionsChange() {
452
+ if (this.dimensionsLayoutScheduled) {
453
+ return;
454
+ }
455
+ this.dimensionsLayoutScheduled = true;
456
+ Promise.resolve().then(() => {
457
+ this.pinToBottom.execute(this.canvas);
458
+ this.dimensionChange();
459
+ this.repositionCutoffs.execute(this.canvas);
460
+ this.repositionBorders.execute(this.canvas);
461
+ this.repositionBackgrounds.execute(this.canvas);
462
+ this.redrawAll.execute(this.canvas);
463
+ this.dimensionsLayoutScheduled = false;
464
+ });
465
+ }
466
+ onTextChange() {
467
+ this.resizeCanvas.execute(this.canvas);
468
+ }
469
+ onObjectListChange() {
470
+ this.redrawAll.execute(this.canvas);
471
+ }
472
+ dimensionChange() {
473
+ const canvasMargin = this.canvas.getMargin();
474
+ const canvasDimensions = this.canvas.getDimensions();
475
+ const allObjects = this.canvas.getObjectsByTypesInternal(ObjectType.TEXT, ObjectType.RICH_TEXT,
476
+ //ObjectType.IMAGE,
477
+ ObjectType.RECTANGLE, ObjectType.VERTICAL_RULE, ObjectType.HORIZONTAL_RULE);
478
+ const bodyWidth = canvasDimensions.widthPx - canvasMargin.right - canvasMargin.left;
479
+ this.canvas.redraw();
480
+ //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.
481
+ this.stackObjects.applyWidths(allObjects, bodyWidth);
482
+ }
483
+ }
484
+
485
+ class UpdateObjectDimensionsLayoutFunction {
486
+ execute(canvas) {
487
+ canvas.getObjectsInternal().forEach((object) => object.updateDimensions());
488
+ }
489
+ }
490
+
447
491
  class StandardLayoutManager extends AbstractLayoutManager {
448
492
  constructor(canvas) {
449
493
  super(canvas);
@@ -456,6 +500,7 @@ class StandardLayoutManager extends AbstractLayoutManager {
456
500
  this.repositionBackgrounds = new RepositionBackgroundsLayoutFunction();
457
501
  this.stackObjects = new StackObjectsLayoutFunction();
458
502
  this.updateObjectDimensions = new UpdateObjectDimensionsLayoutFunction();
503
+ this.layoutBatchScheduled = false;
459
504
  }
460
505
  /**
461
506
  * @override
@@ -497,12 +542,19 @@ class StandardLayoutManager extends AbstractLayoutManager {
497
542
  this.stackAndRedraw();
498
543
  }
499
544
  stackAndRedraw() {
500
- this.stackObjects.execute(this.canvas);
501
- this.repositionCutoffs.execute(this.canvas);
502
- this.repositionBorders.execute(this.canvas);
503
- this.repositionBackgrounds.execute(this.canvas);
504
- this.updateObjectDimensions.execute(this.canvas);
505
- this.redrawAll.execute(this.canvas);
545
+ if (this.layoutBatchScheduled) {
546
+ return;
547
+ }
548
+ this.layoutBatchScheduled = true;
549
+ Promise.resolve().then(() => {
550
+ this.stackObjects.execute(this.canvas);
551
+ this.repositionCutoffs.execute(this.canvas);
552
+ this.repositionBorders.execute(this.canvas);
553
+ this.repositionBackgrounds.execute(this.canvas);
554
+ this.updateObjectDimensions.execute(this.canvas);
555
+ this.redrawAll.execute(this.canvas);
556
+ this.layoutBatchScheduled = false;
557
+ });
506
558
  }
507
559
  }
508
560
 
@@ -969,7 +1021,7 @@ class AbstractCanvasObject {
969
1021
  //no-op
970
1022
  }
971
1023
  preventLeavingCanvas() {
972
- const overflow = this.getCanvasOverflow();
1024
+ const overflow = this.getCanvasOverflowForBoundaryPrevention();
973
1025
  if (overflow.hasOverflow) {
974
1026
  const adjustX = overflow.left || -overflow.right;
975
1027
  const adjustY = overflow.top || -overflow.bottom;
@@ -995,6 +1047,29 @@ class AbstractCanvasObject {
995
1047
  hasOverflow: top > 0 || right > 0 || bottom > 0 || left > 0
996
1048
  };
997
1049
  }
1050
+ getCanvasOverflowForBoundaryPrevention() {
1051
+ // Use a simpler bounding box calculation for boundary prevention
1052
+ // to avoid issues with decorative elements (like ordered list markers)
1053
+ // extending beyond the main object bounds
1054
+ const bound = this.getSimpleBoundingBox();
1055
+ const canvasDimensions = this.canvas.getDimensions();
1056
+ const top = Math.max(0, 0 - bound.top);
1057
+ const right = Math.max(0, bound.left + bound.width - canvasDimensions.widthPx);
1058
+ const bottom = Math.max(0, bound.top + bound.height - canvasDimensions.heightPx);
1059
+ const left = Math.max(0, 0 - bound.left);
1060
+ return {
1061
+ top: top,
1062
+ right: right,
1063
+ bottom: bottom,
1064
+ left: left,
1065
+ hasOverflow: top > 0 || right > 0 || bottom > 0 || left > 0
1066
+ };
1067
+ }
1068
+ getSimpleBoundingBox() {
1069
+ // Default implementation: use normal bounding box
1070
+ // Subclasses can override this to provide simpler bounds for drag boundary checks
1071
+ return this.getBoundingBox();
1072
+ }
998
1073
  centerHorizontallyInternal() {
999
1074
  const canvasWidth = this.canvas.getDimensions().widthPx;
1000
1075
  const objectWidth = this.getDimensions().width.px;
@@ -1042,7 +1117,8 @@ class AbstractCanvasObject {
1042
1117
  mt: options.allowScale,
1043
1118
  mr: options.allowScale,
1044
1119
  mb: options.allowScale,
1045
- mtr: options.allowRotate
1120
+ mtr: options.allowRotate,
1121
+ // dragControl: options.allowMove // Show drag control when movement is allowed
1046
1122
  });
1047
1123
  }
1048
1124
  }
@@ -1763,32 +1839,32 @@ class RichText extends Image {
1763
1839
  }
1764
1840
  }
1765
1841
 
1766
- var ObjectType;
1767
- (function (ObjectType) {
1768
- ObjectType["TEXT"] = "TEXT";
1769
- ObjectType["BORDER"] = "BORDER";
1770
- ObjectType["IMAGE"] = "IMAGE";
1771
- ObjectType["RECTANGLE"] = "RECTANGLE";
1772
- ObjectType["LIBRARY_IMAGE"] = "LIBRARY_IMAGE";
1773
- ObjectType["CUTOFF"] = "CUTOFF";
1774
- ObjectType["VERTICAL_RULE"] = "VERTICAL_RULE";
1775
- ObjectType["HORIZONTAL_RULE"] = "HORIZONTAL_RULE";
1776
- ObjectType["RICH_TEXT"] = "RICH_TEXT";
1777
- ObjectType["BACKGROUND_IMAGE"] = "BACKGROUND_IMAGE";
1778
- })(ObjectType || (ObjectType = {}));
1779
-
1780
1842
  class Text extends AbstractCanvasObject {
1781
1843
  constructor(canvas, fabricObject, persistedField) {
1782
1844
  super(ObjectType.TEXT, canvas, fabricObject, persistedField);
1845
+ this.notifyCanvasTimeout = null;
1846
+ this.NOTIFY_DELAY_MS = 150; // Debounce canvas notifications
1783
1847
  this.canvas = canvas;
1784
1848
  this.fontLibrary = canvas.getFontLibrary();
1785
1849
  this.baseComponentHeight = this.fabricObject.height;
1786
1850
  this.text$ = new BehaviorSubject(this.fabricObject.text);
1787
1851
  this.data.userText = this.fabricObject.text;
1788
1852
  this.textSelection$ = new BehaviorSubject(new TextSelection(this.fabricObject, this.fontLibrary));
1789
- this.fabricObject.set({ strokeWidth: 0 });
1853
+ this.fabricObject.set({
1854
+ strokeWidth: 0,
1855
+ perPixelTargetFind: false, // Disable per-pixel detection for this object
1856
+ padding: 0 // Add padding for larger hover area (Fabric v7 compatibility)
1857
+ });
1790
1858
  this.fabricObject.on("changed", () => this.onTextChange());
1791
1859
  this.fabricObject.on("selection:changed", () => this.onSelectionChange());
1860
+ // Note: Removed containsPoint override to allow Fabric's default bounding box
1861
+ // detection with padding to work properly for hover detection
1862
+ this.fabricObject.on("mousedown", (e) => {
1863
+ var _a;
1864
+ if (!this.fabricObject.isEditing) {
1865
+ (_a = e.e) === null || _a === void 0 ? void 0 : _a.preventDefault();
1866
+ }
1867
+ });
1792
1868
  this.applyBulletListMonkeyPatches();
1793
1869
  }
1794
1870
  /**
@@ -2140,11 +2216,25 @@ class Text extends AbstractCanvasObject {
2140
2216
  getBulletMargin() {
2141
2217
  if (!this.isBulleted())
2142
2218
  return 0;
2143
- let fontSize = 0;
2144
- each(this.fabricObject._textLines, (textLine, lineIndex) => {
2145
- fontSize = Math.max(fontSize, this.fabricObject.getValueOfPropertyAt(lineIndex, 0, "fontSize"));
2146
- });
2147
- return fontSize * 0.6;
2219
+ try {
2220
+ let fontSize = 0;
2221
+ each(this.fabricObject._textLines, (textLine, lineIndex) => {
2222
+ const size = this.fabricObject.getValueOfPropertyAt(lineIndex, 0, "fontSize");
2223
+ // Sanity check fontSize
2224
+ if (isFinite(size) && size > 0 && size < 1000) {
2225
+ fontSize = Math.max(fontSize, size);
2226
+ }
2227
+ });
2228
+ // Additional safety check
2229
+ if (!isFinite(fontSize) || fontSize < 0 || fontSize > 1000) {
2230
+ return 0;
2231
+ }
2232
+ return fontSize * 0.6;
2233
+ }
2234
+ catch (e) {
2235
+ console.warn('Error calculating bullet margin:', e);
2236
+ return 0;
2237
+ }
2148
2238
  }
2149
2239
  /**
2150
2240
  * @override
@@ -2154,10 +2244,35 @@ class Text extends AbstractCanvasObject {
2154
2244
  if (!this.isOrderedList())
2155
2245
  return 0;
2156
2246
  const orderedListMarkerEntities = this.fabricObject._orderedListMarkerEntities;
2157
- let maxValue = max((orderedListMarkerEntities !== null && orderedListMarkerEntities !== void 0 ? orderedListMarkerEntities : [])
2158
- .filter((x) => !!x)
2159
- .map((x) => x.getBoundingRect().width));
2160
- return maxValue ? (maxValue * 1.2) : 0;
2247
+ try {
2248
+ const widths = (orderedListMarkerEntities !== null && orderedListMarkerEntities !== void 0 ? orderedListMarkerEntities : [])
2249
+ .filter((x) => !!x)
2250
+ .map((x) => {
2251
+ var _a;
2252
+ try {
2253
+ const rect = x.getBoundingRect();
2254
+ const width = (_a = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _a !== void 0 ? _a : 0;
2255
+ // Sanity check: if width is invalid, return 0
2256
+ if (!isFinite(width) || width < 0 || width > 1000) {
2257
+ return 0;
2258
+ }
2259
+ return width;
2260
+ }
2261
+ catch (e) {
2262
+ return 0;
2263
+ }
2264
+ });
2265
+ const maxValue = max(widths);
2266
+ // Additional safety: ensure maxValue is a valid number
2267
+ if (!maxValue || !isFinite(maxValue) || maxValue < 0 || maxValue > 1000) {
2268
+ return 0;
2269
+ }
2270
+ return maxValue * 1.2;
2271
+ }
2272
+ catch (e) {
2273
+ console.warn('Error calculating ordered list margin:', e);
2274
+ return 0;
2275
+ }
2161
2276
  }
2162
2277
  /**
2163
2278
  * @override
@@ -2398,6 +2513,7 @@ class Text extends AbstractCanvasObject {
2398
2513
  lockMovementY: true,
2399
2514
  hasControls: false,
2400
2515
  selectable: false,
2516
+ // evented: false, // Don't capture mouse events - allows clicks to pass through to textbox
2401
2517
  originX: "left",
2402
2518
  originY: "top"
2403
2519
  });
@@ -2492,7 +2608,7 @@ class Text extends AbstractCanvasObject {
2492
2608
  text: `${lineNumber}.`
2493
2609
  });
2494
2610
  }
2495
- if (method === "fillText" && this.fabricObject.left <= 3) {
2611
+ if (method === "fillText" && this.isOrderedList() && this.fabricObject.left <= 3) {
2496
2612
  left = left + this.getOrderedListMargin();
2497
2613
  }
2498
2614
  this.fabricObject.dirty = true;
@@ -2504,13 +2620,24 @@ class Text extends AbstractCanvasObject {
2504
2620
  * @inheritDoc
2505
2621
  */
2506
2622
  updateFabricObjectBehavior() {
2623
+ // DON'T call super.updateFabricObjectBehavior() for textboxes
2624
+ // because it locks movement which interferes with text editing in Fabric v7
2507
2625
  super.updateFabricObjectBehavior();
2508
2626
  const profile = this.canvas.getProfile();
2509
2627
  const allowManualPositioning = !this.canvas.getLayoutManager().isEnforcedPositioning;
2628
+ // For textboxes, we keep them moveable but hide all controls except drag control
2629
+ // This allows text editing to work properly while still supporting drag via the icon
2510
2630
  this.fabricObject.set({
2631
+ lockMovementX: false, // Allow dragging
2632
+ lockMovementY: false, // Allow dragging
2511
2633
  lockScalingY: true,
2512
2634
  lockScalingX: this.isPinToBottom(),
2513
- lockScalingFlip: true
2635
+ lockScalingFlip: true,
2636
+ lockRotation: true,
2637
+ selectable: true,
2638
+ editable: false, // Only enable on double-click
2639
+ hoverCursor: "move", // Show move cursor by default
2640
+ moveCursor: "move",
2514
2641
  });
2515
2642
  this.fabricObject.setControlsVisibility({
2516
2643
  tl: false,
@@ -2520,35 +2647,64 @@ class Text extends AbstractCanvasObject {
2520
2647
  ml: allowManualPositioning && profile.allowScale && !this.isPinToBottom(),
2521
2648
  mt: false,
2522
2649
  mr: allowManualPositioning && profile.allowScale && !this.isPinToBottom(),
2523
- mb: false
2650
+ mb: false,
2651
+ mtr: false,
2524
2652
  });
2525
2653
  this.redraw();
2526
2654
  }
2527
2655
  onTextChange() {
2528
2656
  this.data.userText = this.fabricObject.text;
2529
2657
  this.text$.next(this.fabricObject.text);
2530
- if (this.getBoundingBox().width > this.canvas.getDimensions().widthPx) {
2531
- this.fabricObject.set({ left: 0, width: this.canvas.getDimensions().widthPx });
2532
- this.redraw();
2658
+ // Only check width bounds if text is close to canvas edge (performance optimization)
2659
+ const currentLeft = this.fabricObject.left || 0;
2660
+ const canvasWidth = this.canvas.getDimensions().widthPx;
2661
+ // Skip expensive getBoundingBox if object is clearly within bounds
2662
+ if (currentLeft + (this.fabricObject.width || 0) > canvasWidth * 0.9) {
2663
+ if (this.getBoundingBox().width > canvasWidth) {
2664
+ this.fabricObject.set({ left: 0, width: canvasWidth });
2665
+ this.redraw();
2666
+ }
2533
2667
  }
2534
2668
  }
2535
2669
  onDelete() {
2536
2670
  this.removeBullets();
2537
2671
  }
2538
2672
  onSelectionChange() {
2539
- this.updateTextSelection();
2673
+ this.updateTextSelection(false);
2540
2674
  }
2541
2675
  hasTextSelection() {
2542
2676
  return this.canvas.getSelection().isText && this.textSelection$.value.length > 0;
2543
2677
  }
2544
- updateTextSelection() {
2678
+ updateTextSelection(notifyCanvas = true) {
2545
2679
  if (this.canvas.headless)
2546
2680
  return;
2547
2681
  this.textSelection$.next(new TextSelection(this.fabricObject, this.fontLibrary));
2548
- this.notifyCanvasOfTextChanges();
2682
+ if (notifyCanvas) {
2683
+ this.notifyCanvasOfTextChanges();
2684
+ }
2549
2685
  }
2550
2686
  notifyCanvasOfTextChanges() {
2551
- this.canvas.notifyTextChanged();
2687
+ if (this.notifyCanvasTimeout) {
2688
+ clearTimeout(this.notifyCanvasTimeout);
2689
+ }
2690
+ this.notifyCanvasTimeout = setTimeout(() => {
2691
+ this.canvas.notifyTextChanged();
2692
+ this.notifyCanvasTimeout = null;
2693
+ }, this.NOTIFY_DELAY_MS);
2694
+ }
2695
+ /**
2696
+ * @override
2697
+ * Provide a simpler bounding box for drag boundary checks that uses the textbox
2698
+ * dimensions directly instead of getBoundingRect(), which includes ordered list
2699
+ * markers that may extend far beyond the text and cause drag shaking issues.
2700
+ */
2701
+ getSimpleBoundingBox() {
2702
+ return {
2703
+ top: this.fabricObject.top || 0,
2704
+ left: this.fabricObject.left || 0,
2705
+ width: (this.fabricObject.width || 0) * (this.fabricObject.scaleX || 1),
2706
+ height: (this.fabricObject.height || 0) * (this.fabricObject.scaleY || 1)
2707
+ };
2552
2708
  }
2553
2709
  }
2554
2710
  Text.NEW_LINE = "\n";
@@ -2714,6 +2870,7 @@ class AbstractInteractiveCanvas {
2714
2870
  this.devicePixelRatio = devicePixelRatio;
2715
2871
  this.fontLibrary = new FontLibrary();
2716
2872
  this.dimensionChangeRejections$ = new Subject();
2873
+ this.redrawScheduled = false;
2717
2874
  this.IMAGE_CAPTURE_COEFFICIENT = this.calculateImageCaptureCoefficient();
2718
2875
  /**
2719
2876
  * Fabric defaults to only 2 decimal places when saving, which causes havoc on the scaling of large images.
@@ -2735,8 +2892,8 @@ class AbstractInteractiveCanvas {
2735
2892
  //Configure canvas behaviour
2736
2893
  this.fabricCanvas.stateful = true;
2737
2894
  this.fabricCanvas.preserveObjectStacking = true;
2738
- this.fabricCanvas.perPixelTargetFind = true;
2739
- this.fabricCanvas.targetFindTolerance = 4;
2895
+ this.fabricCanvas.perPixelTargetFind = false; // Disabled for better hover tolerance in Fabric v7
2896
+ this.fabricCanvas.targetFindTolerance = 0; // Increased for better hover detection in Fabric v7 (was 4)
2740
2897
  this.setBackgroundColor("#FFFFFF");
2741
2898
  }
2742
2899
  /**
@@ -3040,7 +3197,6 @@ class AbstractInteractiveCanvas {
3040
3197
  */
3041
3198
  addImageObject(imageUrl) {
3042
3199
  return __awaiter(this, void 0, void 0, function* () {
3043
- // Don't let the image be more than half the canvas in size.
3044
3200
  const maxSizeScale = 2;
3045
3201
  const maxWidth = this.dimensions$.value.widthPx / maxSizeScale;
3046
3202
  const options = { crossOrigin: "anonymous" };
@@ -3052,7 +3208,6 @@ class AbstractInteractiveCanvas {
3052
3208
  if (curWidth > maxWidth) {
3053
3209
  newScale = maxWidth / curWidth;
3054
3210
  }
3055
- // Set image properties and add to the canvas
3056
3211
  const fabricImage = new this.fabric.Image(image, {
3057
3212
  id: id,
3058
3213
  scaleX: newScale,
@@ -3072,7 +3227,6 @@ class AbstractInteractiveCanvas {
3072
3227
  return object;
3073
3228
  }
3074
3229
  else {
3075
- // Browser-side logic
3076
3230
  return yield new Promise((resolve) => {
3077
3231
  this.fabric.Image.fromURL(imageUrl, options).then((fabricImage) => {
3078
3232
  const id = this.randomId();
@@ -3124,15 +3278,14 @@ class AbstractInteractiveCanvas {
3124
3278
  fontFamily: profile.defaultFont,
3125
3279
  textAlign: profile.defaultTextAlignment,
3126
3280
  hoverCursor: "move",
3127
- moveCursor: 'move',
3281
+ moveCursor: "move",
3282
+ editable: false,
3283
+ editingBorderColor: '#2196F3',
3128
3284
  multiLine: true,
3129
3285
  lineHeight: profile.defaultLineHeight,
3130
3286
  charSpacing: profile.defaultCharacterSpacing,
3131
- perPixelTargetFind: false,
3132
- targetFindTolerance: 4,
3133
3287
  splitByGrapheme: true,
3134
- editable: false, // Only enable on double-click
3135
- padding: 0,
3288
+ padding: 0, // Increased from 0 for better hover detection in Fabric v7
3136
3289
  cornerSize: 8,
3137
3290
  touchCornerSize: 12
3138
3291
  };
@@ -3581,9 +3734,17 @@ class AbstractInteractiveCanvas {
3581
3734
  }
3582
3735
  /**
3583
3736
  * Redraw the canvas.
3737
+ * Uses requestAnimationFrame to batch multiple redraw calls into a single render.
3584
3738
  */
3585
3739
  redraw() {
3586
- this.fabricCanvas.requestRenderAll();
3740
+ if (this.redrawScheduled) {
3741
+ return;
3742
+ }
3743
+ this.redrawScheduled = true;
3744
+ requestAnimationFrame(() => {
3745
+ this.fabricCanvas.requestRenderAll();
3746
+ this.redrawScheduled = false;
3747
+ });
3587
3748
  }
3588
3749
  syncObjectList() {
3589
3750
  const newObjectList = _(this.fabricCanvas.getObjects())
@@ -3709,7 +3870,6 @@ class AbstractInteractiveCanvas {
3709
3870
  * Update the actual dimensions of the canvas by multiplying the requested dimensions by the current zoom factor.
3710
3871
  */
3711
3872
  updateCanvasDimensions(dims, zoom) {
3712
- this.fabricCanvas.getWidth();
3713
3873
  const width = dims.widthPx * zoom;
3714
3874
  const height = dims.heightPx * zoom;
3715
3875
  this.fabricCanvas.setZoom(zoom);
@@ -3717,37 +3877,6 @@ class AbstractInteractiveCanvas {
3717
3877
  width,
3718
3878
  height
3719
3879
  });
3720
- /* if (this.layoutManager$.value instanceof AdvancedLayoutManager) {
3721
- const scaleFactor = width / oldWidth;
3722
- this.fabricCanvas.calcOffset();
3723
- const objects = this.fabricCanvas.getObjects();
3724
- objects.forEach((obj: any) => {
3725
- obj.set({
3726
- left: obj.left * scaleFactor,
3727
- });
3728
- if (obj.type === "textbox" || obj.type === "text") {
3729
- obj.set({
3730
- scaleX: 1,
3731
- scaleY: 1,
3732
- width: obj.width * scaleFactor,
3733
- });
3734
- obj.initDimensions();
3735
- } else if (obj.type === "border" || obj.type === "rect") {
3736
- obj.set({
3737
- width: obj.width * scaleFactor,
3738
- scaleX: 1,
3739
- strokeUniform: true,
3740
- strokeAlign: "inner",
3741
- });
3742
- } else {
3743
- obj.set({
3744
- scaleX: obj.scaleX * scaleFactor,
3745
- });
3746
- }
3747
- obj.setCoords();
3748
- });
3749
- this.fabricCanvas.requestRenderAll();
3750
- }*/
3751
3880
  }
3752
3881
  onProfileChanged(profile) {
3753
3882
  this.setMultiSelect(profile.allowMultiSelect);
@@ -3902,7 +4031,6 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
3902
4031
  this.fabricCanvas.on("selection:created", (event) => this.onFabricSelectionChanged(event));
3903
4032
  this.fabricCanvas.on("selection:cleared", (event) => this.onFabricSelectionChanged(event));
3904
4033
  this.fabricCanvas.on("text:editing:entered", () => this.onTextEditing());
3905
- this.fabricCanvas.on("text:editing:exited", () => this.onTextEditing());
3906
4034
  this.fabricCanvas.on("text:editing:exited", (event) => {
3907
4035
  this.onTextEditing();
3908
4036
  const target = event.target;
@@ -3911,8 +4039,6 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
3911
4039
  }
3912
4040
  });
3913
4041
  this.fabricCanvas.on("text:changed", () => this.onTextChange());
3914
- // Handle double-click on textbox to enter editing mode
3915
- // @ts-ignore
3916
4042
  this.fabricCanvas.on("mouse:dblclick", (event) => this.onDoubleClick(event));
3917
4043
  }
3918
4044
  /**
@@ -3947,29 +4073,20 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
3947
4073
  selection.setEvent(event);
3948
4074
  }
3949
4075
  this.selection$.next(selection);
3950
- this.enterTextSelectionModeIfRequired();
3951
- }
3952
- enterTextSelectionModeIfRequired() {
3953
- var _a, _b;
3954
- if (!this.profile$.value.enterTextEditImmediatelyOnSelect && !this.layoutManager$.value.enterTextEditImmediatelyOnSelect)
3955
- return;
3956
- if (((_b = (_a = this.selection$.value) === null || _a === void 0 ? void 0 : _a.objects[0]) === null || _b === void 0 ? void 0 : _b.getType()) !== ObjectType.TEXT)
3957
- return;
3958
- const text = this.selection$.value.objects[0];
3959
- text.enterEditing();
3960
4076
  }
3961
4077
  onTextEditing() {
3962
4078
  const selected = this.getFieldsByCanvasObject(this.fabricCanvas.getActiveObjects());
3963
4079
  this.selection$.next(new SelectionData(selected));
3964
4080
  }
3965
4081
  onDoubleClick(event) {
4082
+ var _a, _b;
3966
4083
  const target = event.target;
3967
4084
  if (target && (target.type === 'textbox' || target.type === 'i-text')) {
3968
- if (!target.isEditing) {
4085
+ const targetIText = target;
4086
+ if (!targetIText.isEditing) {
3969
4087
  target.set({ editable: true, hoverCursor: 'text' });
3970
- // target.enterEditing();
3971
- // target.hiddenTextarea?.focus();
3972
- this.enterTextSelectionModeIfRequired();
4088
+ (_a = targetIText.enterEditing) === null || _a === void 0 ? void 0 : _a.call(targetIText);
4089
+ (_b = targetIText.hiddenTextarea) === null || _b === void 0 ? void 0 : _b.focus();
3973
4090
  }
3974
4091
  }
3975
4092
  }