@leapfrog/interactive-canvas 2.0.25-beta-7 → 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,16 +1780,12 @@ 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;
1823
1786
  this.text$ = new BehaviorSubject(this.fabricObject.text);
1824
1787
  this.data.userText = this.fabricObject.text;
1825
1788
  this.textSelection$ = new BehaviorSubject(new TextSelection(this.fabricObject, this.fontLibrary));
1826
- console.log(`inside constructor in text.ts - ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
1827
1789
  this.fabricObject.set({ strokeWidth: 0 });
1828
1790
  this.fabricObject.on("changed", () => this.onTextChange());
1829
1791
  this.fabricObject.on("selection:changed", () => this.onSelectionChange());
@@ -2563,54 +2525,30 @@ class Text extends AbstractCanvasObject {
2563
2525
  this.redraw();
2564
2526
  }
2565
2527
  onTextChange() {
2566
- console.log(`inside onTextChange in text.ts - ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
2567
2528
  this.data.userText = this.fabricObject.text;
2568
2529
  this.text$.next(this.fabricObject.text);
2569
- // Only check width bounds if text is close to canvas edge (performance optimization)
2570
- const currentLeft = this.fabricObject.left || 0;
2571
- const canvasWidth = this.canvas.getDimensions().widthPx;
2572
- // Skip expensive getBoundingBox if object is clearly within bounds
2573
- if (currentLeft + (this.fabricObject.width || 0) > canvasWidth * 0.9) {
2574
- console.log(`inside if onTextChange in text.ts - ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
2575
- if (this.getBoundingBox().width > canvasWidth) {
2576
- this.fabricObject.set({ left: 0, width: canvasWidth });
2577
- this.redraw();
2578
- }
2530
+ if (this.getBoundingBox().width > this.canvas.getDimensions().widthPx) {
2531
+ this.fabricObject.set({ left: 0, width: this.canvas.getDimensions().widthPx });
2532
+ this.redraw();
2579
2533
  }
2580
- console.log(`end of onTextChange in text.ts - ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
2581
2534
  }
2582
2535
  onDelete() {
2583
2536
  this.removeBullets();
2584
2537
  }
2585
2538
  onSelectionChange() {
2586
- console.log(`start of onSelectionChange in text.ts - ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
2587
- this.updateTextSelection(false);
2539
+ this.updateTextSelection();
2588
2540
  }
2589
2541
  hasTextSelection() {
2590
2542
  return this.canvas.getSelection().isText && this.textSelection$.value.length > 0;
2591
2543
  }
2592
- updateTextSelection(notifyCanvas = true) {
2593
- console.log(`start of updateTextSelection in text.ts - ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
2544
+ updateTextSelection() {
2594
2545
  if (this.canvas.headless)
2595
2546
  return;
2596
2547
  this.textSelection$.next(new TextSelection(this.fabricObject, this.fontLibrary));
2597
- if (notifyCanvas) {
2598
- console.log(`inside if in text.ts - ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
2599
- this.notifyCanvasOfTextChanges();
2600
- }
2548
+ this.notifyCanvasOfTextChanges();
2601
2549
  }
2602
2550
  notifyCanvasOfTextChanges() {
2603
- console.log(`start of notifyCanvasOfTextChanges in text.ts - ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
2604
- if (this.notifyCanvasTimeout) {
2605
- console.log(`inside timeout if in text.ts - ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
2606
- clearTimeout(this.notifyCanvasTimeout);
2607
- }
2608
- this.notifyCanvasTimeout = setTimeout(() => {
2609
- console.log(`start of setTimeout in text.ts - ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
2610
- this.canvas.notifyTextChanged();
2611
- this.notifyCanvasTimeout = null;
2612
- }, this.NOTIFY_DELAY_MS);
2613
- console.log(`end of notifyCanvasOfTextChanges in text.ts - ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
2551
+ this.canvas.notifyTextChanged();
2614
2552
  }
2615
2553
  }
2616
2554
  Text.NEW_LINE = "\n";
@@ -2618,7 +2556,6 @@ class TextSelection {
2618
2556
  constructor(fabricObject, fontLibrary) {
2619
2557
  this.fabricObject = fabricObject;
2620
2558
  this.fontLibrary = fontLibrary;
2621
- console.log(`constructor of textselection in text.ts - ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
2622
2559
  this.start = this.fabricObject.selectionStart;
2623
2560
  this.end = this.fabricObject.selectionEnd;
2624
2561
  this.length = this.end - this.start;
@@ -2777,7 +2714,6 @@ class AbstractInteractiveCanvas {
2777
2714
  this.devicePixelRatio = devicePixelRatio;
2778
2715
  this.fontLibrary = new FontLibrary();
2779
2716
  this.dimensionChangeRejections$ = new Subject();
2780
- this.redrawScheduled = false;
2781
2717
  this.IMAGE_CAPTURE_COEFFICIENT = this.calculateImageCaptureCoefficient();
2782
2718
  /**
2783
2719
  * Fabric defaults to only 2 decimal places when saving, which causes havoc on the scaling of large images.
@@ -2800,7 +2736,7 @@ class AbstractInteractiveCanvas {
2800
2736
  this.fabricCanvas.stateful = true;
2801
2737
  this.fabricCanvas.preserveObjectStacking = true;
2802
2738
  this.fabricCanvas.perPixelTargetFind = true;
2803
- this.fabricCanvas.targetFindTolerance = 10;
2739
+ this.fabricCanvas.targetFindTolerance = 4;
2804
2740
  this.setBackgroundColor("#FFFFFF");
2805
2741
  }
2806
2742
  /**
@@ -3194,7 +3130,11 @@ class AbstractInteractiveCanvas {
3194
3130
  charSpacing: profile.defaultCharacterSpacing,
3195
3131
  perPixelTargetFind: false,
3196
3132
  targetFindTolerance: 4,
3197
- splitByGrapheme: true
3133
+ splitByGrapheme: true,
3134
+ editable: false, // Only enable on double-click
3135
+ padding: 0,
3136
+ cornerSize: 8,
3137
+ touchCornerSize: 12
3198
3138
  };
3199
3139
  const fabricTextbox = new this.fabric.Textbox("Text goes here", textOptions);
3200
3140
  this.fabricCanvas.add(fabricTextbox);
@@ -3643,16 +3583,7 @@ class AbstractInteractiveCanvas {
3643
3583
  * Redraw the canvas.
3644
3584
  */
3645
3585
  redraw() {
3646
- //this.fabricCanvas.requestRenderAll();
3647
- if (this.redrawScheduled) {
3648
- return; // Already scheduled, don't schedule again
3649
- }
3650
- this.redrawScheduled = true;
3651
- // Use requestAnimationFrame for smoother rendering and automatic batching
3652
- requestAnimationFrame(() => {
3653
- this.fabricCanvas.requestRenderAll();
3654
- this.redrawScheduled = false;
3655
- });
3586
+ this.fabricCanvas.requestRenderAll();
3656
3587
  }
3657
3588
  syncObjectList() {
3658
3589
  const newObjectList = _(this.fabricCanvas.getObjects())
@@ -3778,6 +3709,7 @@ class AbstractInteractiveCanvas {
3778
3709
  * Update the actual dimensions of the canvas by multiplying the requested dimensions by the current zoom factor.
3779
3710
  */
3780
3711
  updateCanvasDimensions(dims, zoom) {
3712
+ const oldWidth = this.fabricCanvas.getWidth();
3781
3713
  const width = dims.widthPx * zoom;
3782
3714
  const height = dims.heightPx * zoom;
3783
3715
  this.fabricCanvas.setZoom(zoom);
@@ -3785,6 +3717,34 @@ class AbstractInteractiveCanvas {
3785
3717
  width,
3786
3718
  height
3787
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();
3788
3748
  }
3789
3749
  onProfileChanged(profile) {
3790
3750
  this.setMultiSelect(profile.allowMultiSelect);
@@ -3939,8 +3899,18 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
3939
3899
  this.fabricCanvas.on("selection:created", (event) => this.onFabricSelectionChanged(event));
3940
3900
  this.fabricCanvas.on("selection:cleared", (event) => this.onFabricSelectionChanged(event));
3941
3901
  this.fabricCanvas.on("text:editing:entered", () => this.onTextEditing());
3942
- 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
+ });
3943
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));
3944
3914
  }
3945
3915
  /**
3946
3916
  * @override
@@ -3968,7 +3938,6 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
3968
3938
  });
3969
3939
  }
3970
3940
  onFabricSelectionChanged(event) {
3971
- console.log(`start of onfabricselectionchange in ic.ts - ${MouseEvent}- ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
3972
3941
  const selected = this.getFieldsByCanvasObject(this.fabricCanvas.getActiveObjects());
3973
3942
  const selection = new SelectionData(selected);
3974
3943
  if (event) {
@@ -3979,7 +3948,6 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
3979
3948
  }
3980
3949
  enterTextSelectionModeIfRequired() {
3981
3950
  var _a, _b;
3982
- console.log(`start of enterTextSelectionModeIfRequired in IC.ts - ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
3983
3951
  if (!this.profile$.value.enterTextEditImmediatelyOnSelect && !this.layoutManager$.value.enterTextEditImmediatelyOnSelect)
3984
3952
  return;
3985
3953
  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)
@@ -3988,10 +3956,20 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
3988
3956
  text.enterEditing();
3989
3957
  }
3990
3958
  onTextEditing() {
3991
- console.log(`start of onTextEditing in IC.ts - ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
3992
3959
  const selected = this.getFieldsByCanvasObject(this.fabricCanvas.getActiveObjects());
3993
3960
  this.selection$.next(new SelectionData(selected));
3994
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
+ }
3995
3973
  }
3996
3974
 
3997
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,16 +1801,12 @@ 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;
1844
1807
  this.text$ = new rxjs.BehaviorSubject(this.fabricObject.text);
1845
1808
  this.data.userText = this.fabricObject.text;
1846
1809
  this.textSelection$ = new rxjs.BehaviorSubject(new TextSelection(this.fabricObject, this.fontLibrary));
1847
- console.log(`inside constructor in text.ts - ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
1848
1810
  this.fabricObject.set({ strokeWidth: 0 });
1849
1811
  this.fabricObject.on("changed", () => this.onTextChange());
1850
1812
  this.fabricObject.on("selection:changed", () => this.onSelectionChange());
@@ -2584,54 +2546,30 @@ class Text extends AbstractCanvasObject {
2584
2546
  this.redraw();
2585
2547
  }
2586
2548
  onTextChange() {
2587
- console.log(`inside onTextChange in text.ts - ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
2588
2549
  this.data.userText = this.fabricObject.text;
2589
2550
  this.text$.next(this.fabricObject.text);
2590
- // Only check width bounds if text is close to canvas edge (performance optimization)
2591
- const currentLeft = this.fabricObject.left || 0;
2592
- const canvasWidth = this.canvas.getDimensions().widthPx;
2593
- // Skip expensive getBoundingBox if object is clearly within bounds
2594
- if (currentLeft + (this.fabricObject.width || 0) > canvasWidth * 0.9) {
2595
- console.log(`inside if onTextChange in text.ts - ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
2596
- if (this.getBoundingBox().width > canvasWidth) {
2597
- this.fabricObject.set({ left: 0, width: canvasWidth });
2598
- this.redraw();
2599
- }
2551
+ if (this.getBoundingBox().width > this.canvas.getDimensions().widthPx) {
2552
+ this.fabricObject.set({ left: 0, width: this.canvas.getDimensions().widthPx });
2553
+ this.redraw();
2600
2554
  }
2601
- console.log(`end of onTextChange in text.ts - ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
2602
2555
  }
2603
2556
  onDelete() {
2604
2557
  this.removeBullets();
2605
2558
  }
2606
2559
  onSelectionChange() {
2607
- console.log(`start of onSelectionChange in text.ts - ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
2608
- this.updateTextSelection(false);
2560
+ this.updateTextSelection();
2609
2561
  }
2610
2562
  hasTextSelection() {
2611
2563
  return this.canvas.getSelection().isText && this.textSelection$.value.length > 0;
2612
2564
  }
2613
- updateTextSelection(notifyCanvas = true) {
2614
- console.log(`start of updateTextSelection in text.ts - ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
2565
+ updateTextSelection() {
2615
2566
  if (this.canvas.headless)
2616
2567
  return;
2617
2568
  this.textSelection$.next(new TextSelection(this.fabricObject, this.fontLibrary));
2618
- if (notifyCanvas) {
2619
- console.log(`inside if in text.ts - ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
2620
- this.notifyCanvasOfTextChanges();
2621
- }
2569
+ this.notifyCanvasOfTextChanges();
2622
2570
  }
2623
2571
  notifyCanvasOfTextChanges() {
2624
- console.log(`start of notifyCanvasOfTextChanges in text.ts - ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
2625
- if (this.notifyCanvasTimeout) {
2626
- console.log(`inside timeout if in text.ts - ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
2627
- clearTimeout(this.notifyCanvasTimeout);
2628
- }
2629
- this.notifyCanvasTimeout = setTimeout(() => {
2630
- console.log(`start of setTimeout in text.ts - ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
2631
- this.canvas.notifyTextChanged();
2632
- this.notifyCanvasTimeout = null;
2633
- }, this.NOTIFY_DELAY_MS);
2634
- console.log(`end of notifyCanvasOfTextChanges in text.ts - ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
2572
+ this.canvas.notifyTextChanged();
2635
2573
  }
2636
2574
  }
2637
2575
  Text.NEW_LINE = "\n";
@@ -2639,7 +2577,6 @@ class TextSelection {
2639
2577
  constructor(fabricObject, fontLibrary) {
2640
2578
  this.fabricObject = fabricObject;
2641
2579
  this.fontLibrary = fontLibrary;
2642
- console.log(`constructor of textselection in text.ts - ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
2643
2580
  this.start = this.fabricObject.selectionStart;
2644
2581
  this.end = this.fabricObject.selectionEnd;
2645
2582
  this.length = this.end - this.start;
@@ -2798,7 +2735,6 @@ class AbstractInteractiveCanvas {
2798
2735
  this.devicePixelRatio = devicePixelRatio;
2799
2736
  this.fontLibrary = new FontLibrary();
2800
2737
  this.dimensionChangeRejections$ = new rxjs.Subject();
2801
- this.redrawScheduled = false;
2802
2738
  this.IMAGE_CAPTURE_COEFFICIENT = this.calculateImageCaptureCoefficient();
2803
2739
  /**
2804
2740
  * Fabric defaults to only 2 decimal places when saving, which causes havoc on the scaling of large images.
@@ -2821,7 +2757,7 @@ class AbstractInteractiveCanvas {
2821
2757
  this.fabricCanvas.stateful = true;
2822
2758
  this.fabricCanvas.preserveObjectStacking = true;
2823
2759
  this.fabricCanvas.perPixelTargetFind = true;
2824
- this.fabricCanvas.targetFindTolerance = 10;
2760
+ this.fabricCanvas.targetFindTolerance = 4;
2825
2761
  this.setBackgroundColor("#FFFFFF");
2826
2762
  }
2827
2763
  /**
@@ -3215,7 +3151,11 @@ class AbstractInteractiveCanvas {
3215
3151
  charSpacing: profile.defaultCharacterSpacing,
3216
3152
  perPixelTargetFind: false,
3217
3153
  targetFindTolerance: 4,
3218
- splitByGrapheme: true
3154
+ splitByGrapheme: true,
3155
+ editable: false, // Only enable on double-click
3156
+ padding: 0,
3157
+ cornerSize: 8,
3158
+ touchCornerSize: 12
3219
3159
  };
3220
3160
  const fabricTextbox = new this.fabric.Textbox("Text goes here", textOptions);
3221
3161
  this.fabricCanvas.add(fabricTextbox);
@@ -3664,16 +3604,7 @@ class AbstractInteractiveCanvas {
3664
3604
  * Redraw the canvas.
3665
3605
  */
3666
3606
  redraw() {
3667
- //this.fabricCanvas.requestRenderAll();
3668
- if (this.redrawScheduled) {
3669
- return; // Already scheduled, don't schedule again
3670
- }
3671
- this.redrawScheduled = true;
3672
- // Use requestAnimationFrame for smoother rendering and automatic batching
3673
- requestAnimationFrame(() => {
3674
- this.fabricCanvas.requestRenderAll();
3675
- this.redrawScheduled = false;
3676
- });
3607
+ this.fabricCanvas.requestRenderAll();
3677
3608
  }
3678
3609
  syncObjectList() {
3679
3610
  const newObjectList = _(this.fabricCanvas.getObjects())
@@ -3799,6 +3730,7 @@ class AbstractInteractiveCanvas {
3799
3730
  * Update the actual dimensions of the canvas by multiplying the requested dimensions by the current zoom factor.
3800
3731
  */
3801
3732
  updateCanvasDimensions(dims, zoom) {
3733
+ const oldWidth = this.fabricCanvas.getWidth();
3802
3734
  const width = dims.widthPx * zoom;
3803
3735
  const height = dims.heightPx * zoom;
3804
3736
  this.fabricCanvas.setZoom(zoom);
@@ -3806,6 +3738,34 @@ class AbstractInteractiveCanvas {
3806
3738
  width,
3807
3739
  height
3808
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();
3809
3769
  }
3810
3770
  onProfileChanged(profile) {
3811
3771
  this.setMultiSelect(profile.allowMultiSelect);
@@ -3960,8 +3920,18 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
3960
3920
  this.fabricCanvas.on("selection:created", (event) => this.onFabricSelectionChanged(event));
3961
3921
  this.fabricCanvas.on("selection:cleared", (event) => this.onFabricSelectionChanged(event));
3962
3922
  this.fabricCanvas.on("text:editing:entered", () => this.onTextEditing());
3963
- 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
+ });
3964
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));
3965
3935
  }
3966
3936
  /**
3967
3937
  * @override
@@ -3989,7 +3959,6 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
3989
3959
  });
3990
3960
  }
3991
3961
  onFabricSelectionChanged(event) {
3992
- console.log(`start of onfabricselectionchange in ic.ts - ${MouseEvent}- ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
3993
3962
  const selected = this.getFieldsByCanvasObject(this.fabricCanvas.getActiveObjects());
3994
3963
  const selection = new SelectionData(selected);
3995
3964
  if (event) {
@@ -4000,7 +3969,6 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
4000
3969
  }
4001
3970
  enterTextSelectionModeIfRequired() {
4002
3971
  var _a, _b;
4003
- console.log(`start of enterTextSelectionModeIfRequired in IC.ts - ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
4004
3972
  if (!this.profile$.value.enterTextEditImmediatelyOnSelect && !this.layoutManager$.value.enterTextEditImmediatelyOnSelect)
4005
3973
  return;
4006
3974
  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)
@@ -4009,10 +3977,20 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
4009
3977
  text.enterEditing();
4010
3978
  }
4011
3979
  onTextEditing() {
4012
- console.log(`start of onTextEditing in IC.ts - ${new Date().toLocaleString()}-${new Date().getMilliseconds()}`);
4013
3980
  const selected = this.getFieldsByCanvasObject(this.fabricCanvas.getActiveObjects());
4014
3981
  this.selection$.next(new SelectionData(selected));
4015
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
+ }
4016
3994
  }
4017
3995
 
4018
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-7",
3
+ "version": "2.0.25-beta-8",
4
4
  "description": "Leapfrog interactive canvas",
5
5
  "files": [
6
6
  "dist"