@leapfrog/interactive-canvas 2.0.25-beta-6 → 2.0.25-beta-8

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,7 +340,6 @@ export declare abstract class AbstractInteractiveCanvas implements IInteractiveC
340
340
  * @inheritDoc
341
341
  */
342
342
  cloneObject(sourceObject: FabricObject, objectData: CanvasObjectData): Promise<void>;
343
- private redrawScheduled;
344
343
  /**
345
344
  * Redraw the canvas.
346
345
  */
@@ -22,4 +22,5 @@ export declare class InteractiveCanvas extends AbstractInteractiveCanvas {
22
22
  private onFabricSelectionChanged;
23
23
  private enterTextSelectionModeIfRequired;
24
24
  private onTextEditing;
25
+ private onDoubleClick;
25
26
  }
@@ -232,18 +232,9 @@ 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
- // Only update coords for objects affected by height change (pinned to bottom, etc.)
236
- // This is much faster than updating all objects on slow machines
237
- canvas.getObjectsInternal().forEach((object) => {
238
- var _a;
239
- const canvasObj = canvas.getObjectsByTypeInternal(object.type).find(o => o.getId() === object.getId());
240
- // Only update coords for objects that are positioned relative to canvas bottom or that might be affected
241
- if (canvasObj && (((_a = canvasObj.isPinToBottom) === null || _a === void 0 ? void 0 : _a.call(canvasObj)) || object.top > currentDimensions.heightPx * 0.5)) {
242
- object.setCoords();
243
- }
244
- });
245
- canvas.redraw();
246
235
  }
236
+ canvas.getObjectsInternal().forEach((object) => object.setCoords());
237
+ canvas.redraw();
247
238
  }
248
239
  }
249
240
 
@@ -271,22 +262,13 @@ class AdvancedLayoutManager extends AbstractLayoutManager {
271
262
  this.repositionCutoffs = new RepositionCutoffsLayoutFunction();
272
263
  this.repositionBackgrounds = new RepositionBackgroundsLayoutFunction();
273
264
  this.resizeCanvas = new ResizeCanvasLayoutFunction();
274
- this.dimensionsLayoutScheduled = false;
275
265
  }
276
266
  onCanvasDimensionsChange() {
277
- // Batch dimension changes to avoid redundant layout passes
278
- if (this.dimensionsLayoutScheduled) {
279
- return;
280
- }
281
- this.dimensionsLayoutScheduled = true;
282
- Promise.resolve().then(() => {
283
- this.pinToBottom.execute(this.canvas);
284
- this.repositionCutoffs.execute(this.canvas);
285
- this.repositionBorders.execute(this.canvas);
286
- this.repositionBackgrounds.execute(this.canvas);
287
- this.redrawAll.execute(this.canvas);
288
- this.dimensionsLayoutScheduled = false;
289
- });
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);
290
272
  }
291
273
  onTextChange() {
292
274
  this.resizeCanvas.execute(this.canvas);
@@ -314,9 +296,7 @@ class StackObjectsLayoutFunction {
314
296
  const footerObjects = objects[0];
315
297
  const bodyObjects = objects[1];
316
298
  const bodyWidth = canvasDimensions.widthPx - canvasMargin.right - canvasMargin.left;
317
- //canvas.redraw();
318
- // No need to redraw here - it will be called by the layout manager after all layout functions complete
319
- // This reduces redundant render passes significantly
299
+ canvas.redraw();
320
300
  //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.
321
301
  this.applyWidths(allObjects, bodyWidth);
322
302
  const bodyRows = this.getRows(bodyObjects);
@@ -403,11 +383,7 @@ class StackObjectsLayoutFunction {
403
383
  object.setPosition({
404
384
  width: actualWidth
405
385
  });
406
- _(objects).forEach((object) => {
407
- if (object.getType() !== ObjectType.VERTICAL_RULE) {
408
- object.updateDimensions();
409
- }
410
- });
386
+ object.updateDimensions();
411
387
  });
412
388
  }
413
389
  adjustCanvasSize(canvas, requiredHeight) {
@@ -480,7 +456,6 @@ class StandardLayoutManager extends AbstractLayoutManager {
480
456
  this.repositionBackgrounds = new RepositionBackgroundsLayoutFunction();
481
457
  this.stackObjects = new StackObjectsLayoutFunction();
482
458
  this.updateObjectDimensions = new UpdateObjectDimensionsLayoutFunction();
483
- this.layoutBatchScheduled = false;
484
459
  }
485
460
  /**
486
461
  * @override
@@ -522,21 +497,12 @@ class StandardLayoutManager extends AbstractLayoutManager {
522
497
  this.stackAndRedraw();
523
498
  }
524
499
  stackAndRedraw() {
525
- // Batch multiple layout requests into a single pass
526
- if (this.layoutBatchScheduled) {
527
- return;
528
- }
529
- this.layoutBatchScheduled = true;
530
- // Use microtask to batch synchronous calls, but still execute before next frame
531
- Promise.resolve().then(() => {
532
- this.stackObjects.execute(this.canvas);
533
- this.repositionCutoffs.execute(this.canvas);
534
- this.repositionBorders.execute(this.canvas);
535
- this.repositionBackgrounds.execute(this.canvas);
536
- this.updateObjectDimensions.execute(this.canvas);
537
- this.redrawAll.execute(this.canvas);
538
- this.layoutBatchScheduled = false;
539
- });
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);
540
506
  }
541
507
  }
542
508
 
@@ -1814,9 +1780,6 @@ var ObjectType;
1814
1780
  class Text extends AbstractCanvasObject {
1815
1781
  constructor(canvas, fabricObject, persistedField) {
1816
1782
  super(ObjectType.TEXT, canvas, fabricObject, persistedField);
1817
- //private notifyCanvasTimeout: NodeJS.Timeout | null = null;
1818
- this.notifyCanvasTimeout = null;
1819
- this.NOTIFY_DELAY_MS = 150; // Debounce canvas notifications
1820
1783
  this.canvas = canvas;
1821
1784
  this.fontLibrary = canvas.getFontLibrary();
1822
1785
  this.baseComponentHeight = this.fabricObject.height;
@@ -2564,42 +2527,28 @@ class Text extends AbstractCanvasObject {
2564
2527
  onTextChange() {
2565
2528
  this.data.userText = this.fabricObject.text;
2566
2529
  this.text$.next(this.fabricObject.text);
2567
- // Only check width bounds if text is close to canvas edge (performance optimization)
2568
- const currentLeft = this.fabricObject.left || 0;
2569
- const canvasWidth = this.canvas.getDimensions().widthPx;
2570
- // Skip expensive getBoundingBox if object is clearly within bounds
2571
- if (currentLeft + (this.fabricObject.width || 0) > canvasWidth * 0.9) {
2572
- if (this.getBoundingBox().width > canvasWidth) {
2573
- this.fabricObject.set({ left: 0, width: canvasWidth });
2574
- this.redraw();
2575
- }
2530
+ if (this.getBoundingBox().width > this.canvas.getDimensions().widthPx) {
2531
+ this.fabricObject.set({ left: 0, width: this.canvas.getDimensions().widthPx });
2532
+ this.redraw();
2576
2533
  }
2577
2534
  }
2578
2535
  onDelete() {
2579
2536
  this.removeBullets();
2580
2537
  }
2581
2538
  onSelectionChange() {
2582
- this.updateTextSelection(false);
2539
+ this.updateTextSelection();
2583
2540
  }
2584
2541
  hasTextSelection() {
2585
2542
  return this.canvas.getSelection().isText && this.textSelection$.value.length > 0;
2586
2543
  }
2587
- updateTextSelection(notifyCanvas = true) {
2544
+ updateTextSelection() {
2588
2545
  if (this.canvas.headless)
2589
2546
  return;
2590
2547
  this.textSelection$.next(new TextSelection(this.fabricObject, this.fontLibrary));
2591
- if (notifyCanvas) {
2592
- this.notifyCanvasOfTextChanges();
2593
- }
2548
+ this.notifyCanvasOfTextChanges();
2594
2549
  }
2595
2550
  notifyCanvasOfTextChanges() {
2596
- if (this.notifyCanvasTimeout) {
2597
- clearTimeout(this.notifyCanvasTimeout);
2598
- }
2599
- this.notifyCanvasTimeout = setTimeout(() => {
2600
- this.canvas.notifyTextChanged();
2601
- this.notifyCanvasTimeout = null;
2602
- }, this.NOTIFY_DELAY_MS);
2551
+ this.canvas.notifyTextChanged();
2603
2552
  }
2604
2553
  }
2605
2554
  Text.NEW_LINE = "\n";
@@ -2765,7 +2714,6 @@ class AbstractInteractiveCanvas {
2765
2714
  this.devicePixelRatio = devicePixelRatio;
2766
2715
  this.fontLibrary = new FontLibrary();
2767
2716
  this.dimensionChangeRejections$ = new Subject();
2768
- this.redrawScheduled = false;
2769
2717
  this.IMAGE_CAPTURE_COEFFICIENT = this.calculateImageCaptureCoefficient();
2770
2718
  /**
2771
2719
  * Fabric defaults to only 2 decimal places when saving, which causes havoc on the scaling of large images.
@@ -2788,7 +2736,7 @@ class AbstractInteractiveCanvas {
2788
2736
  this.fabricCanvas.stateful = true;
2789
2737
  this.fabricCanvas.preserveObjectStacking = true;
2790
2738
  this.fabricCanvas.perPixelTargetFind = true;
2791
- this.fabricCanvas.targetFindTolerance = 10;
2739
+ this.fabricCanvas.targetFindTolerance = 4;
2792
2740
  this.setBackgroundColor("#FFFFFF");
2793
2741
  }
2794
2742
  /**
@@ -3182,7 +3130,11 @@ class AbstractInteractiveCanvas {
3182
3130
  charSpacing: profile.defaultCharacterSpacing,
3183
3131
  perPixelTargetFind: false,
3184
3132
  targetFindTolerance: 4,
3185
- splitByGrapheme: true
3133
+ splitByGrapheme: true,
3134
+ editable: false, // Only enable on double-click
3135
+ padding: 0,
3136
+ cornerSize: 8,
3137
+ touchCornerSize: 12
3186
3138
  };
3187
3139
  const fabricTextbox = new this.fabric.Textbox("Text goes here", textOptions);
3188
3140
  this.fabricCanvas.add(fabricTextbox);
@@ -3631,16 +3583,7 @@ class AbstractInteractiveCanvas {
3631
3583
  * Redraw the canvas.
3632
3584
  */
3633
3585
  redraw() {
3634
- //this.fabricCanvas.requestRenderAll();
3635
- if (this.redrawScheduled) {
3636
- return; // Already scheduled, don't schedule again
3637
- }
3638
- this.redrawScheduled = true;
3639
- // Use requestAnimationFrame for smoother rendering and automatic batching
3640
- requestAnimationFrame(() => {
3641
- this.fabricCanvas.requestRenderAll();
3642
- this.redrawScheduled = false;
3643
- });
3586
+ this.fabricCanvas.requestRenderAll();
3644
3587
  }
3645
3588
  syncObjectList() {
3646
3589
  const newObjectList = _(this.fabricCanvas.getObjects())
@@ -3766,6 +3709,7 @@ class AbstractInteractiveCanvas {
3766
3709
  * Update the actual dimensions of the canvas by multiplying the requested dimensions by the current zoom factor.
3767
3710
  */
3768
3711
  updateCanvasDimensions(dims, zoom) {
3712
+ const oldWidth = this.fabricCanvas.getWidth();
3769
3713
  const width = dims.widthPx * zoom;
3770
3714
  const height = dims.heightPx * zoom;
3771
3715
  this.fabricCanvas.setZoom(zoom);
@@ -3773,6 +3717,34 @@ class AbstractInteractiveCanvas {
3773
3717
  width,
3774
3718
  height
3775
3719
  });
3720
+ const scaleFactor = width / oldWidth;
3721
+ const objects = this.fabricCanvas.getObjects();
3722
+ objects.forEach((obj) => {
3723
+ obj.set({
3724
+ left: obj.left * scaleFactor,
3725
+ });
3726
+ if (obj.type === "textbox" || obj.type === "text") {
3727
+ obj.set({
3728
+ scaleX: 1,
3729
+ scaleY: 1,
3730
+ width: obj.width * scaleFactor,
3731
+ });
3732
+ }
3733
+ else if (obj.type === "border" || obj.type === "rect") {
3734
+ obj.set({
3735
+ width: obj.width * scaleFactor,
3736
+ scaleX: 1,
3737
+ strokeUniform: true,
3738
+ });
3739
+ }
3740
+ else {
3741
+ obj.set({
3742
+ scaleX: obj.scaleX * scaleFactor,
3743
+ });
3744
+ }
3745
+ obj.setCoords();
3746
+ });
3747
+ this.fabricCanvas.requestRenderAll();
3776
3748
  }
3777
3749
  onProfileChanged(profile) {
3778
3750
  this.setMultiSelect(profile.allowMultiSelect);
@@ -3927,8 +3899,18 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
3927
3899
  this.fabricCanvas.on("selection:created", (event) => this.onFabricSelectionChanged(event));
3928
3900
  this.fabricCanvas.on("selection:cleared", (event) => this.onFabricSelectionChanged(event));
3929
3901
  this.fabricCanvas.on("text:editing:entered", () => this.onTextEditing());
3930
- this.fabricCanvas.on("text:editing:exited", () => this.onTextEditing());
3902
+ //this.fabricCanvas.on("text:editing:exited", () => this.onTextEditing());
3903
+ this.fabricCanvas.on("text:editing:exited", (event) => {
3904
+ this.onTextEditing();
3905
+ const target = event.target;
3906
+ if (target && (target.type === 'textbox' || target.type === 'i-text')) {
3907
+ target.set({ editable: false, hoverCursor: 'move' });
3908
+ }
3909
+ });
3931
3910
  this.fabricCanvas.on("text:changed", () => this.onTextChange());
3911
+ // Handle double-click on textbox to enter editing mode
3912
+ // @ts-ignore
3913
+ this.fabricCanvas.on("mouse:dblclick", (event) => this.onDoubleClick(event));
3932
3914
  }
3933
3915
  /**
3934
3916
  * @override
@@ -3977,6 +3959,17 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
3977
3959
  const selected = this.getFieldsByCanvasObject(this.fabricCanvas.getActiveObjects());
3978
3960
  this.selection$.next(new SelectionData(selected));
3979
3961
  }
3962
+ onDoubleClick(event) {
3963
+ const target = event.target;
3964
+ if (target && (target.type === 'textbox' || target.type === 'i-text')) {
3965
+ if (!target.isEditing) {
3966
+ target.set({ editable: true, hoverCursor: 'text' });
3967
+ // target.enterEditing();
3968
+ // target.hiddenTextarea?.focus();
3969
+ this.enterTextSelectionModeIfRequired();
3970
+ }
3971
+ }
3972
+ }
3980
3973
  }
3981
3974
 
3982
3975
  export { AbstractCanvasObject, AbstractInteractiveCanvas, AbstractLayoutManager, AdvancedLayoutManager, BackgroundImage, Border, CanvasObjectData, Cutoff, DefaultCanvasProfile, Font, FontLibrary, HeadlessInteractiveCanvas, HorizontalRule, Image, ImageType, InteractiveCanvas, LayoutManager, MillimeterDimensions, ObjectType, PixelCanvasDimensions, Print6ColCanvasDimensions, Print8ColCanvasDimensions, Rectangle, RichText, SelectionData, StandardLayoutManager, Text, UNKNOWN_FONT, UnknownFont, VerticalRule, isMutableBackgroundColor, isMutableColor, isMutableImage, isMutablePosition, isMutableStroke, isMutableText, isMutableTextStyle };
@@ -253,18 +253,9 @@ 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
- // Only update coords for objects affected by height change (pinned to bottom, etc.)
257
- // This is much faster than updating all objects on slow machines
258
- canvas.getObjectsInternal().forEach((object) => {
259
- var _a;
260
- const canvasObj = canvas.getObjectsByTypeInternal(object.type).find(o => o.getId() === object.getId());
261
- // Only update coords for objects that are positioned relative to canvas bottom or that might be affected
262
- if (canvasObj && (((_a = canvasObj.isPinToBottom) === null || _a === void 0 ? void 0 : _a.call(canvasObj)) || object.top > currentDimensions.heightPx * 0.5)) {
263
- object.setCoords();
264
- }
265
- });
266
- canvas.redraw();
267
256
  }
257
+ canvas.getObjectsInternal().forEach((object) => object.setCoords());
258
+ canvas.redraw();
268
259
  }
269
260
  }
270
261
 
@@ -292,22 +283,13 @@ class AdvancedLayoutManager extends AbstractLayoutManager {
292
283
  this.repositionCutoffs = new RepositionCutoffsLayoutFunction();
293
284
  this.repositionBackgrounds = new RepositionBackgroundsLayoutFunction();
294
285
  this.resizeCanvas = new ResizeCanvasLayoutFunction();
295
- this.dimensionsLayoutScheduled = false;
296
286
  }
297
287
  onCanvasDimensionsChange() {
298
- // Batch dimension changes to avoid redundant layout passes
299
- if (this.dimensionsLayoutScheduled) {
300
- return;
301
- }
302
- this.dimensionsLayoutScheduled = true;
303
- Promise.resolve().then(() => {
304
- this.pinToBottom.execute(this.canvas);
305
- this.repositionCutoffs.execute(this.canvas);
306
- this.repositionBorders.execute(this.canvas);
307
- this.repositionBackgrounds.execute(this.canvas);
308
- this.redrawAll.execute(this.canvas);
309
- this.dimensionsLayoutScheduled = false;
310
- });
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);
311
293
  }
312
294
  onTextChange() {
313
295
  this.resizeCanvas.execute(this.canvas);
@@ -335,9 +317,7 @@ class StackObjectsLayoutFunction {
335
317
  const footerObjects = objects[0];
336
318
  const bodyObjects = objects[1];
337
319
  const bodyWidth = canvasDimensions.widthPx - canvasMargin.right - canvasMargin.left;
338
- //canvas.redraw();
339
- // No need to redraw here - it will be called by the layout manager after all layout functions complete
340
- // This reduces redundant render passes significantly
320
+ canvas.redraw();
341
321
  //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.
342
322
  this.applyWidths(allObjects, bodyWidth);
343
323
  const bodyRows = this.getRows(bodyObjects);
@@ -424,11 +404,7 @@ class StackObjectsLayoutFunction {
424
404
  object.setPosition({
425
405
  width: actualWidth
426
406
  });
427
- _(objects).forEach((object) => {
428
- if (object.getType() !== exports.ObjectType.VERTICAL_RULE) {
429
- object.updateDimensions();
430
- }
431
- });
407
+ object.updateDimensions();
432
408
  });
433
409
  }
434
410
  adjustCanvasSize(canvas, requiredHeight) {
@@ -501,7 +477,6 @@ class StandardLayoutManager extends AbstractLayoutManager {
501
477
  this.repositionBackgrounds = new RepositionBackgroundsLayoutFunction();
502
478
  this.stackObjects = new StackObjectsLayoutFunction();
503
479
  this.updateObjectDimensions = new UpdateObjectDimensionsLayoutFunction();
504
- this.layoutBatchScheduled = false;
505
480
  }
506
481
  /**
507
482
  * @override
@@ -543,21 +518,12 @@ class StandardLayoutManager extends AbstractLayoutManager {
543
518
  this.stackAndRedraw();
544
519
  }
545
520
  stackAndRedraw() {
546
- // Batch multiple layout requests into a single pass
547
- if (this.layoutBatchScheduled) {
548
- return;
549
- }
550
- this.layoutBatchScheduled = true;
551
- // Use microtask to batch synchronous calls, but still execute before next frame
552
- Promise.resolve().then(() => {
553
- this.stackObjects.execute(this.canvas);
554
- this.repositionCutoffs.execute(this.canvas);
555
- this.repositionBorders.execute(this.canvas);
556
- this.repositionBackgrounds.execute(this.canvas);
557
- this.updateObjectDimensions.execute(this.canvas);
558
- this.redrawAll.execute(this.canvas);
559
- this.layoutBatchScheduled = false;
560
- });
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);
561
527
  }
562
528
  }
563
529
 
@@ -1835,9 +1801,6 @@ exports.ObjectType = void 0;
1835
1801
  class Text extends AbstractCanvasObject {
1836
1802
  constructor(canvas, fabricObject, persistedField) {
1837
1803
  super(exports.ObjectType.TEXT, canvas, fabricObject, persistedField);
1838
- //private notifyCanvasTimeout: NodeJS.Timeout | null = null;
1839
- this.notifyCanvasTimeout = null;
1840
- this.NOTIFY_DELAY_MS = 150; // Debounce canvas notifications
1841
1804
  this.canvas = canvas;
1842
1805
  this.fontLibrary = canvas.getFontLibrary();
1843
1806
  this.baseComponentHeight = this.fabricObject.height;
@@ -2585,42 +2548,28 @@ class Text extends AbstractCanvasObject {
2585
2548
  onTextChange() {
2586
2549
  this.data.userText = this.fabricObject.text;
2587
2550
  this.text$.next(this.fabricObject.text);
2588
- // Only check width bounds if text is close to canvas edge (performance optimization)
2589
- const currentLeft = this.fabricObject.left || 0;
2590
- const canvasWidth = this.canvas.getDimensions().widthPx;
2591
- // Skip expensive getBoundingBox if object is clearly within bounds
2592
- if (currentLeft + (this.fabricObject.width || 0) > canvasWidth * 0.9) {
2593
- if (this.getBoundingBox().width > canvasWidth) {
2594
- this.fabricObject.set({ left: 0, width: canvasWidth });
2595
- this.redraw();
2596
- }
2551
+ if (this.getBoundingBox().width > this.canvas.getDimensions().widthPx) {
2552
+ this.fabricObject.set({ left: 0, width: this.canvas.getDimensions().widthPx });
2553
+ this.redraw();
2597
2554
  }
2598
2555
  }
2599
2556
  onDelete() {
2600
2557
  this.removeBullets();
2601
2558
  }
2602
2559
  onSelectionChange() {
2603
- this.updateTextSelection(false);
2560
+ this.updateTextSelection();
2604
2561
  }
2605
2562
  hasTextSelection() {
2606
2563
  return this.canvas.getSelection().isText && this.textSelection$.value.length > 0;
2607
2564
  }
2608
- updateTextSelection(notifyCanvas = true) {
2565
+ updateTextSelection() {
2609
2566
  if (this.canvas.headless)
2610
2567
  return;
2611
2568
  this.textSelection$.next(new TextSelection(this.fabricObject, this.fontLibrary));
2612
- if (notifyCanvas) {
2613
- this.notifyCanvasOfTextChanges();
2614
- }
2569
+ this.notifyCanvasOfTextChanges();
2615
2570
  }
2616
2571
  notifyCanvasOfTextChanges() {
2617
- if (this.notifyCanvasTimeout) {
2618
- clearTimeout(this.notifyCanvasTimeout);
2619
- }
2620
- this.notifyCanvasTimeout = setTimeout(() => {
2621
- this.canvas.notifyTextChanged();
2622
- this.notifyCanvasTimeout = null;
2623
- }, this.NOTIFY_DELAY_MS);
2572
+ this.canvas.notifyTextChanged();
2624
2573
  }
2625
2574
  }
2626
2575
  Text.NEW_LINE = "\n";
@@ -2786,7 +2735,6 @@ class AbstractInteractiveCanvas {
2786
2735
  this.devicePixelRatio = devicePixelRatio;
2787
2736
  this.fontLibrary = new FontLibrary();
2788
2737
  this.dimensionChangeRejections$ = new rxjs.Subject();
2789
- this.redrawScheduled = false;
2790
2738
  this.IMAGE_CAPTURE_COEFFICIENT = this.calculateImageCaptureCoefficient();
2791
2739
  /**
2792
2740
  * Fabric defaults to only 2 decimal places when saving, which causes havoc on the scaling of large images.
@@ -2809,7 +2757,7 @@ class AbstractInteractiveCanvas {
2809
2757
  this.fabricCanvas.stateful = true;
2810
2758
  this.fabricCanvas.preserveObjectStacking = true;
2811
2759
  this.fabricCanvas.perPixelTargetFind = true;
2812
- this.fabricCanvas.targetFindTolerance = 10;
2760
+ this.fabricCanvas.targetFindTolerance = 4;
2813
2761
  this.setBackgroundColor("#FFFFFF");
2814
2762
  }
2815
2763
  /**
@@ -3203,7 +3151,11 @@ class AbstractInteractiveCanvas {
3203
3151
  charSpacing: profile.defaultCharacterSpacing,
3204
3152
  perPixelTargetFind: false,
3205
3153
  targetFindTolerance: 4,
3206
- splitByGrapheme: true
3154
+ splitByGrapheme: true,
3155
+ editable: false, // Only enable on double-click
3156
+ padding: 0,
3157
+ cornerSize: 8,
3158
+ touchCornerSize: 12
3207
3159
  };
3208
3160
  const fabricTextbox = new this.fabric.Textbox("Text goes here", textOptions);
3209
3161
  this.fabricCanvas.add(fabricTextbox);
@@ -3652,16 +3604,7 @@ class AbstractInteractiveCanvas {
3652
3604
  * Redraw the canvas.
3653
3605
  */
3654
3606
  redraw() {
3655
- //this.fabricCanvas.requestRenderAll();
3656
- if (this.redrawScheduled) {
3657
- return; // Already scheduled, don't schedule again
3658
- }
3659
- this.redrawScheduled = true;
3660
- // Use requestAnimationFrame for smoother rendering and automatic batching
3661
- requestAnimationFrame(() => {
3662
- this.fabricCanvas.requestRenderAll();
3663
- this.redrawScheduled = false;
3664
- });
3607
+ this.fabricCanvas.requestRenderAll();
3665
3608
  }
3666
3609
  syncObjectList() {
3667
3610
  const newObjectList = _(this.fabricCanvas.getObjects())
@@ -3787,6 +3730,7 @@ class AbstractInteractiveCanvas {
3787
3730
  * Update the actual dimensions of the canvas by multiplying the requested dimensions by the current zoom factor.
3788
3731
  */
3789
3732
  updateCanvasDimensions(dims, zoom) {
3733
+ const oldWidth = this.fabricCanvas.getWidth();
3790
3734
  const width = dims.widthPx * zoom;
3791
3735
  const height = dims.heightPx * zoom;
3792
3736
  this.fabricCanvas.setZoom(zoom);
@@ -3794,6 +3738,34 @@ class AbstractInteractiveCanvas {
3794
3738
  width,
3795
3739
  height
3796
3740
  });
3741
+ const scaleFactor = width / oldWidth;
3742
+ const objects = this.fabricCanvas.getObjects();
3743
+ objects.forEach((obj) => {
3744
+ obj.set({
3745
+ left: obj.left * scaleFactor,
3746
+ });
3747
+ if (obj.type === "textbox" || obj.type === "text") {
3748
+ obj.set({
3749
+ scaleX: 1,
3750
+ scaleY: 1,
3751
+ width: obj.width * scaleFactor,
3752
+ });
3753
+ }
3754
+ else if (obj.type === "border" || obj.type === "rect") {
3755
+ obj.set({
3756
+ width: obj.width * scaleFactor,
3757
+ scaleX: 1,
3758
+ strokeUniform: true,
3759
+ });
3760
+ }
3761
+ else {
3762
+ obj.set({
3763
+ scaleX: obj.scaleX * scaleFactor,
3764
+ });
3765
+ }
3766
+ obj.setCoords();
3767
+ });
3768
+ this.fabricCanvas.requestRenderAll();
3797
3769
  }
3798
3770
  onProfileChanged(profile) {
3799
3771
  this.setMultiSelect(profile.allowMultiSelect);
@@ -3948,8 +3920,18 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
3948
3920
  this.fabricCanvas.on("selection:created", (event) => this.onFabricSelectionChanged(event));
3949
3921
  this.fabricCanvas.on("selection:cleared", (event) => this.onFabricSelectionChanged(event));
3950
3922
  this.fabricCanvas.on("text:editing:entered", () => this.onTextEditing());
3951
- this.fabricCanvas.on("text:editing:exited", () => this.onTextEditing());
3923
+ //this.fabricCanvas.on("text:editing:exited", () => this.onTextEditing());
3924
+ this.fabricCanvas.on("text:editing:exited", (event) => {
3925
+ this.onTextEditing();
3926
+ const target = event.target;
3927
+ if (target && (target.type === 'textbox' || target.type === 'i-text')) {
3928
+ target.set({ editable: false, hoverCursor: 'move' });
3929
+ }
3930
+ });
3952
3931
  this.fabricCanvas.on("text:changed", () => this.onTextChange());
3932
+ // Handle double-click on textbox to enter editing mode
3933
+ // @ts-ignore
3934
+ this.fabricCanvas.on("mouse:dblclick", (event) => this.onDoubleClick(event));
3953
3935
  }
3954
3936
  /**
3955
3937
  * @override
@@ -3998,6 +3980,17 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
3998
3980
  const selected = this.getFieldsByCanvasObject(this.fabricCanvas.getActiveObjects());
3999
3981
  this.selection$.next(new SelectionData(selected));
4000
3982
  }
3983
+ onDoubleClick(event) {
3984
+ const target = event.target;
3985
+ if (target && (target.type === 'textbox' || target.type === 'i-text')) {
3986
+ if (!target.isEditing) {
3987
+ target.set({ editable: true, hoverCursor: 'text' });
3988
+ // target.enterEditing();
3989
+ // target.hiddenTextarea?.focus();
3990
+ this.enterTextSelectionModeIfRequired();
3991
+ }
3992
+ }
3993
+ }
4001
3994
  }
4002
3995
 
4003
3996
  exports.AbstractCanvasObject = AbstractCanvasObject;
@@ -9,7 +9,6 @@ export declare class AdvancedLayoutManager extends AbstractLayoutManager {
9
9
  private repositionCutoffs;
10
10
  private repositionBackgrounds;
11
11
  private resizeCanvas;
12
- private dimensionsLayoutScheduled;
13
12
  constructor(canvas: IInteractiveCanvas);
14
13
  onCanvasDimensionsChange(): void;
15
14
  onTextChange(): void;
@@ -36,6 +36,5 @@ export declare class StandardLayoutManager extends AbstractLayoutManager {
36
36
  * @override
37
37
  */
38
38
  onMarginChange(): void;
39
- private layoutBatchScheduled;
40
39
  private stackAndRedraw;
41
40
  }
@@ -63,8 +63,6 @@ export declare class Text extends AbstractCanvasObject<ExtendedFabricTextbox> im
63
63
  private readonly text$;
64
64
  private readonly textSelection$;
65
65
  static readonly NEW_LINE: string;
66
- private notifyCanvasTimeout;
67
- private readonly NOTIFY_DELAY_MS;
68
66
  constructor(canvas: IInteractiveCanvas, fabricObject: fabric.Textbox, persistedField?: CanvasObjectData);
69
67
  /**
70
68
  * @override
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leapfrog/interactive-canvas",
3
- "version": "2.0.25-beta-6",
3
+ "version": "2.0.25-beta-8",
4
4
  "description": "Leapfrog interactive canvas",
5
5
  "files": [
6
6
  "dist"