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

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,10 +340,8 @@ 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
- * Uses requestAnimationFrame to batch multiple redraw calls into a single render.
347
345
  */
348
346
  redraw(): void;
349
347
  syncObjectList(): void;
@@ -20,6 +20,7 @@ export declare class InteractiveCanvas extends AbstractInteractiveCanvas {
20
20
  */
21
21
  redo(): Promise<void>;
22
22
  private onFabricSelectionChanged;
23
+ private enterTextSelectionModeIfRequired;
23
24
  private onTextEditing;
24
25
  private onDoubleClick;
25
26
  }
@@ -232,15 +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
- 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();
243
235
  }
236
+ canvas.getObjectsInternal().forEach((object) => object.setCoords());
237
+ canvas.redraw();
244
238
  }
245
239
  }
246
240
 
@@ -268,21 +262,13 @@ class AdvancedLayoutManager extends AbstractLayoutManager {
268
262
  this.repositionCutoffs = new RepositionCutoffsLayoutFunction();
269
263
  this.repositionBackgrounds = new RepositionBackgroundsLayoutFunction();
270
264
  this.resizeCanvas = new ResizeCanvasLayoutFunction();
271
- this.dimensionsLayoutScheduled = false;
272
265
  }
273
266
  onCanvasDimensionsChange() {
274
- if (this.dimensionsLayoutScheduled) {
275
- return;
276
- }
277
- this.dimensionsLayoutScheduled = true;
278
- Promise.resolve().then(() => {
279
- this.pinToBottom.execute(this.canvas);
280
- this.repositionCutoffs.execute(this.canvas);
281
- this.repositionBorders.execute(this.canvas);
282
- this.repositionBackgrounds.execute(this.canvas);
283
- this.redrawAll.execute(this.canvas);
284
- this.dimensionsLayoutScheduled = false;
285
- });
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);
286
272
  }
287
273
  onTextChange() {
288
274
  this.resizeCanvas.execute(this.canvas);
@@ -310,6 +296,7 @@ class StackObjectsLayoutFunction {
310
296
  const footerObjects = objects[0];
311
297
  const bodyObjects = objects[1];
312
298
  const bodyWidth = canvasDimensions.widthPx - canvasMargin.right - canvasMargin.left;
299
+ canvas.redraw();
313
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.
314
301
  this.applyWidths(allObjects, bodyWidth);
315
302
  const bodyRows = this.getRows(bodyObjects);
@@ -396,11 +383,7 @@ class StackObjectsLayoutFunction {
396
383
  object.setPosition({
397
384
  width: actualWidth
398
385
  });
399
- });
400
- _(objects).forEach((object) => {
401
- if (object.getType() !== ObjectType.VERTICAL_RULE) {
402
- object.updateDimensions();
403
- }
386
+ object.updateDimensions();
404
387
  });
405
388
  }
406
389
  adjustCanvasSize(canvas, requiredHeight) {
@@ -473,7 +456,6 @@ class StandardLayoutManager extends AbstractLayoutManager {
473
456
  this.repositionBackgrounds = new RepositionBackgroundsLayoutFunction();
474
457
  this.stackObjects = new StackObjectsLayoutFunction();
475
458
  this.updateObjectDimensions = new UpdateObjectDimensionsLayoutFunction();
476
- this.layoutBatchScheduled = false;
477
459
  }
478
460
  /**
479
461
  * @override
@@ -515,19 +497,12 @@ class StandardLayoutManager extends AbstractLayoutManager {
515
497
  this.stackAndRedraw();
516
498
  }
517
499
  stackAndRedraw() {
518
- if (this.layoutBatchScheduled) {
519
- return;
520
- }
521
- this.layoutBatchScheduled = true;
522
- Promise.resolve().then(() => {
523
- this.stackObjects.execute(this.canvas);
524
- this.repositionCutoffs.execute(this.canvas);
525
- this.repositionBorders.execute(this.canvas);
526
- this.repositionBackgrounds.execute(this.canvas);
527
- this.updateObjectDimensions.execute(this.canvas);
528
- this.redrawAll.execute(this.canvas);
529
- this.layoutBatchScheduled = false;
530
- });
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);
531
506
  }
532
507
  }
533
508
 
@@ -1067,8 +1042,7 @@ class AbstractCanvasObject {
1067
1042
  mt: options.allowScale,
1068
1043
  mr: options.allowScale,
1069
1044
  mb: options.allowScale,
1070
- mtr: options.allowRotate,
1071
- // dragControl: options.allowMove // Show drag control when movement is allowed
1045
+ mtr: options.allowRotate
1072
1046
  });
1073
1047
  }
1074
1048
  }
@@ -1806,42 +1780,15 @@ var ObjectType;
1806
1780
  class Text extends AbstractCanvasObject {
1807
1781
  constructor(canvas, fabricObject, persistedField) {
1808
1782
  super(ObjectType.TEXT, canvas, fabricObject, persistedField);
1809
- this.notifyCanvasTimeout = null;
1810
- this.NOTIFY_DELAY_MS = 150; // Debounce canvas notifications
1811
1783
  this.canvas = canvas;
1812
1784
  this.fontLibrary = canvas.getFontLibrary();
1813
1785
  this.baseComponentHeight = this.fabricObject.height;
1814
1786
  this.text$ = new BehaviorSubject(this.fabricObject.text);
1815
1787
  this.data.userText = this.fabricObject.text;
1816
1788
  this.textSelection$ = new BehaviorSubject(new TextSelection(this.fabricObject, this.fontLibrary));
1817
- this.fabricObject.set({
1818
- strokeWidth: 0,
1819
- perPixelTargetFind: false // Disable per-pixel detection for this object
1820
- });
1789
+ this.fabricObject.set({ strokeWidth: 0 });
1821
1790
  this.fabricObject.on("changed", () => this.onTextChange());
1822
1791
  this.fabricObject.on("selection:changed", () => this.onSelectionChange());
1823
- this.fabricObject.containsPoint.bind(this.fabricObject);
1824
- this.fabricObject.containsPoint = function (point) {
1825
- const coords = this.getCoords();
1826
- if (coords.length !== 4)
1827
- return false;
1828
- let inside = false;
1829
- for (let i = 0, j = coords.length - 1; i < coords.length; j = i++) {
1830
- const xi = coords[i].x, yi = coords[i].y;
1831
- const xj = coords[j].x, yj = coords[j].y;
1832
- const intersect = ((yi > point.y) !== (yj > point.y))
1833
- && (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);
1834
- if (intersect)
1835
- inside = !inside;
1836
- }
1837
- return inside;
1838
- };
1839
- this.fabricObject.on("mousedown", (e) => {
1840
- var _a;
1841
- if (!this.fabricObject.isEditing) {
1842
- (_a = e.e) === null || _a === void 0 ? void 0 : _a.preventDefault();
1843
- }
1844
- });
1845
1792
  this.applyBulletListMonkeyPatches();
1846
1793
  }
1847
1794
  /**
@@ -2451,7 +2398,6 @@ class Text extends AbstractCanvasObject {
2451
2398
  lockMovementY: true,
2452
2399
  hasControls: false,
2453
2400
  selectable: false,
2454
- // evented: false, // Don't capture mouse events - allows clicks to pass through to textbox
2455
2401
  originX: "left",
2456
2402
  originY: "top"
2457
2403
  });
@@ -2546,7 +2492,7 @@ class Text extends AbstractCanvasObject {
2546
2492
  text: `${lineNumber}.`
2547
2493
  });
2548
2494
  }
2549
- if (method === "fillText" && this.isOrderedList() && this.fabricObject.left <= 3) {
2495
+ if (method === "fillText" && this.fabricObject.left <= 3) {
2550
2496
  left = left + this.getOrderedListMargin();
2551
2497
  }
2552
2498
  this.fabricObject.dirty = true;
@@ -2558,24 +2504,13 @@ class Text extends AbstractCanvasObject {
2558
2504
  * @inheritDoc
2559
2505
  */
2560
2506
  updateFabricObjectBehavior() {
2561
- // DON'T call super.updateFabricObjectBehavior() for textboxes
2562
- // because it locks movement which interferes with text editing in Fabric v7
2563
2507
  super.updateFabricObjectBehavior();
2564
2508
  const profile = this.canvas.getProfile();
2565
2509
  const allowManualPositioning = !this.canvas.getLayoutManager().isEnforcedPositioning;
2566
- // For textboxes, we keep them moveable but hide all controls except drag control
2567
- // This allows text editing to work properly while still supporting drag via the icon
2568
2510
  this.fabricObject.set({
2569
- lockMovementX: false, // Allow dragging
2570
- lockMovementY: false, // Allow dragging
2571
2511
  lockScalingY: true,
2572
2512
  lockScalingX: this.isPinToBottom(),
2573
- lockScalingFlip: true,
2574
- lockRotation: true,
2575
- selectable: true,
2576
- editable: false, // Only enable on double-click
2577
- hoverCursor: "move", // Show move cursor by default
2578
- moveCursor: "move",
2513
+ lockScalingFlip: true
2579
2514
  });
2580
2515
  this.fabricObject.setControlsVisibility({
2581
2516
  tl: false,
@@ -2585,50 +2520,35 @@ class Text extends AbstractCanvasObject {
2585
2520
  ml: allowManualPositioning && profile.allowScale && !this.isPinToBottom(),
2586
2521
  mt: false,
2587
2522
  mr: allowManualPositioning && profile.allowScale && !this.isPinToBottom(),
2588
- mb: false,
2589
- mtr: false,
2523
+ mb: false
2590
2524
  });
2591
2525
  this.redraw();
2592
2526
  }
2593
2527
  onTextChange() {
2594
2528
  this.data.userText = this.fabricObject.text;
2595
2529
  this.text$.next(this.fabricObject.text);
2596
- // Only check width bounds if text is close to canvas edge (performance optimization)
2597
- const currentLeft = this.fabricObject.left || 0;
2598
- const canvasWidth = this.canvas.getDimensions().widthPx;
2599
- // Skip expensive getBoundingBox if object is clearly within bounds
2600
- if (currentLeft + (this.fabricObject.width || 0) > canvasWidth * 0.9) {
2601
- if (this.getBoundingBox().width > canvasWidth) {
2602
- this.fabricObject.set({ left: 0, width: canvasWidth });
2603
- this.redraw();
2604
- }
2530
+ if (this.getBoundingBox().width > this.canvas.getDimensions().widthPx) {
2531
+ this.fabricObject.set({ left: 0, width: this.canvas.getDimensions().widthPx });
2532
+ this.redraw();
2605
2533
  }
2606
2534
  }
2607
2535
  onDelete() {
2608
2536
  this.removeBullets();
2609
2537
  }
2610
2538
  onSelectionChange() {
2611
- this.updateTextSelection(false);
2539
+ this.updateTextSelection();
2612
2540
  }
2613
2541
  hasTextSelection() {
2614
2542
  return this.canvas.getSelection().isText && this.textSelection$.value.length > 0;
2615
2543
  }
2616
- updateTextSelection(notifyCanvas = true) {
2544
+ updateTextSelection() {
2617
2545
  if (this.canvas.headless)
2618
2546
  return;
2619
2547
  this.textSelection$.next(new TextSelection(this.fabricObject, this.fontLibrary));
2620
- if (notifyCanvas) {
2621
- this.notifyCanvasOfTextChanges();
2622
- }
2548
+ this.notifyCanvasOfTextChanges();
2623
2549
  }
2624
2550
  notifyCanvasOfTextChanges() {
2625
- if (this.notifyCanvasTimeout) {
2626
- clearTimeout(this.notifyCanvasTimeout);
2627
- }
2628
- this.notifyCanvasTimeout = setTimeout(() => {
2629
- this.canvas.notifyTextChanged();
2630
- this.notifyCanvasTimeout = null;
2631
- }, this.NOTIFY_DELAY_MS);
2551
+ this.canvas.notifyTextChanged();
2632
2552
  }
2633
2553
  }
2634
2554
  Text.NEW_LINE = "\n";
@@ -2794,7 +2714,6 @@ class AbstractInteractiveCanvas {
2794
2714
  this.devicePixelRatio = devicePixelRatio;
2795
2715
  this.fontLibrary = new FontLibrary();
2796
2716
  this.dimensionChangeRejections$ = new Subject();
2797
- this.redrawScheduled = false;
2798
2717
  this.IMAGE_CAPTURE_COEFFICIENT = this.calculateImageCaptureCoefficient();
2799
2718
  /**
2800
2719
  * Fabric defaults to only 2 decimal places when saving, which causes havoc on the scaling of large images.
@@ -2817,7 +2736,7 @@ class AbstractInteractiveCanvas {
2817
2736
  this.fabricCanvas.stateful = true;
2818
2737
  this.fabricCanvas.preserveObjectStacking = true;
2819
2738
  this.fabricCanvas.perPixelTargetFind = true;
2820
- this.fabricCanvas.targetFindTolerance = 4; // Reduced tolerance for more precise text selection
2739
+ this.fabricCanvas.targetFindTolerance = 4;
2821
2740
  this.setBackgroundColor("#FFFFFF");
2822
2741
  }
2823
2742
  /**
@@ -3121,6 +3040,7 @@ class AbstractInteractiveCanvas {
3121
3040
  */
3122
3041
  addImageObject(imageUrl) {
3123
3042
  return __awaiter(this, void 0, void 0, function* () {
3043
+ // Don't let the image be more than half the canvas in size.
3124
3044
  const maxSizeScale = 2;
3125
3045
  const maxWidth = this.dimensions$.value.widthPx / maxSizeScale;
3126
3046
  const options = { crossOrigin: "anonymous" };
@@ -3132,6 +3052,7 @@ class AbstractInteractiveCanvas {
3132
3052
  if (curWidth > maxWidth) {
3133
3053
  newScale = maxWidth / curWidth;
3134
3054
  }
3055
+ // Set image properties and add to the canvas
3135
3056
  const fabricImage = new this.fabric.Image(image, {
3136
3057
  id: id,
3137
3058
  scaleX: newScale,
@@ -3151,6 +3072,7 @@ class AbstractInteractiveCanvas {
3151
3072
  return object;
3152
3073
  }
3153
3074
  else {
3075
+ // Browser-side logic
3154
3076
  return yield new Promise((resolve) => {
3155
3077
  this.fabric.Image.fromURL(imageUrl, options).then((fabricImage) => {
3156
3078
  const id = this.randomId();
@@ -3202,13 +3124,14 @@ class AbstractInteractiveCanvas {
3202
3124
  fontFamily: profile.defaultFont,
3203
3125
  textAlign: profile.defaultTextAlignment,
3204
3126
  hoverCursor: "move",
3205
- moveCursor: "move",
3206
- editable: false,
3207
- editingBorderColor: '#2196F3',
3127
+ moveCursor: 'move',
3208
3128
  multiLine: true,
3209
3129
  lineHeight: profile.defaultLineHeight,
3210
3130
  charSpacing: profile.defaultCharacterSpacing,
3131
+ perPixelTargetFind: false,
3132
+ targetFindTolerance: 4,
3211
3133
  splitByGrapheme: true,
3134
+ editable: false, // Only enable on double-click
3212
3135
  padding: 0,
3213
3136
  cornerSize: 8,
3214
3137
  touchCornerSize: 12
@@ -3658,17 +3581,9 @@ class AbstractInteractiveCanvas {
3658
3581
  }
3659
3582
  /**
3660
3583
  * Redraw the canvas.
3661
- * Uses requestAnimationFrame to batch multiple redraw calls into a single render.
3662
3584
  */
3663
3585
  redraw() {
3664
- if (this.redrawScheduled) {
3665
- return;
3666
- }
3667
- this.redrawScheduled = true;
3668
- requestAnimationFrame(() => {
3669
- this.fabricCanvas.requestRenderAll();
3670
- this.redrawScheduled = false;
3671
- });
3586
+ this.fabricCanvas.requestRenderAll();
3672
3587
  }
3673
3588
  syncObjectList() {
3674
3589
  const newObjectList = _(this.fabricCanvas.getObjects())
@@ -3794,6 +3709,7 @@ class AbstractInteractiveCanvas {
3794
3709
  * Update the actual dimensions of the canvas by multiplying the requested dimensions by the current zoom factor.
3795
3710
  */
3796
3711
  updateCanvasDimensions(dims, zoom) {
3712
+ this.fabricCanvas.getWidth();
3797
3713
  const width = dims.widthPx * zoom;
3798
3714
  const height = dims.heightPx * zoom;
3799
3715
  this.fabricCanvas.setZoom(zoom);
@@ -3801,6 +3717,37 @@ class AbstractInteractiveCanvas {
3801
3717
  width,
3802
3718
  height
3803
3719
  });
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
+ }*/
3804
3751
  }
3805
3752
  onProfileChanged(profile) {
3806
3753
  this.setMultiSelect(profile.allowMultiSelect);
@@ -3955,6 +3902,7 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
3955
3902
  this.fabricCanvas.on("selection:created", (event) => this.onFabricSelectionChanged(event));
3956
3903
  this.fabricCanvas.on("selection:cleared", (event) => this.onFabricSelectionChanged(event));
3957
3904
  this.fabricCanvas.on("text:editing:entered", () => this.onTextEditing());
3905
+ this.fabricCanvas.on("text:editing:exited", () => this.onTextEditing());
3958
3906
  this.fabricCanvas.on("text:editing:exited", (event) => {
3959
3907
  this.onTextEditing();
3960
3908
  const target = event.target;
@@ -3963,6 +3911,8 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
3963
3911
  }
3964
3912
  });
3965
3913
  this.fabricCanvas.on("text:changed", () => this.onTextChange());
3914
+ // Handle double-click on textbox to enter editing mode
3915
+ // @ts-ignore
3966
3916
  this.fabricCanvas.on("mouse:dblclick", (event) => this.onDoubleClick(event));
3967
3917
  }
3968
3918
  /**
@@ -3997,20 +3947,29 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
3997
3947
  selection.setEvent(event);
3998
3948
  }
3999
3949
  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();
4000
3960
  }
4001
3961
  onTextEditing() {
4002
3962
  const selected = this.getFieldsByCanvasObject(this.fabricCanvas.getActiveObjects());
4003
3963
  this.selection$.next(new SelectionData(selected));
4004
3964
  }
4005
3965
  onDoubleClick(event) {
4006
- var _a, _b;
4007
3966
  const target = event.target;
4008
3967
  if (target && (target.type === 'textbox' || target.type === 'i-text')) {
4009
- const targetIText = target;
4010
- if (!targetIText.isEditing) {
3968
+ if (!target.isEditing) {
4011
3969
  target.set({ editable: true, hoverCursor: 'text' });
4012
- (_a = targetIText.enterEditing) === null || _a === void 0 ? void 0 : _a.call(targetIText);
4013
- (_b = targetIText.hiddenTextarea) === null || _b === void 0 ? void 0 : _b.focus();
3970
+ // target.enterEditing();
3971
+ // target.hiddenTextarea?.focus();
3972
+ this.enterTextSelectionModeIfRequired();
4014
3973
  }
4015
3974
  }
4016
3975
  }
@@ -253,15 +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
- canvas.getObjectsInternal().forEach((object) => {
257
- var _a;
258
- const canvasObj = canvas.getObjectsByTypeInternal(object.type).find(o => o.getId() === object.getId());
259
- if (canvasObj && (((_a = canvasObj.isPinToBottom) === null || _a === void 0 ? void 0 : _a.call(canvasObj)) || object.top > currentDimensions.heightPx * 0.5)) {
260
- object.setCoords();
261
- }
262
- });
263
- canvas.redraw();
264
256
  }
257
+ canvas.getObjectsInternal().forEach((object) => object.setCoords());
258
+ canvas.redraw();
265
259
  }
266
260
  }
267
261
 
@@ -289,21 +283,13 @@ class AdvancedLayoutManager extends AbstractLayoutManager {
289
283
  this.repositionCutoffs = new RepositionCutoffsLayoutFunction();
290
284
  this.repositionBackgrounds = new RepositionBackgroundsLayoutFunction();
291
285
  this.resizeCanvas = new ResizeCanvasLayoutFunction();
292
- this.dimensionsLayoutScheduled = false;
293
286
  }
294
287
  onCanvasDimensionsChange() {
295
- if (this.dimensionsLayoutScheduled) {
296
- return;
297
- }
298
- this.dimensionsLayoutScheduled = true;
299
- Promise.resolve().then(() => {
300
- this.pinToBottom.execute(this.canvas);
301
- this.repositionCutoffs.execute(this.canvas);
302
- this.repositionBorders.execute(this.canvas);
303
- this.repositionBackgrounds.execute(this.canvas);
304
- this.redrawAll.execute(this.canvas);
305
- this.dimensionsLayoutScheduled = false;
306
- });
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);
307
293
  }
308
294
  onTextChange() {
309
295
  this.resizeCanvas.execute(this.canvas);
@@ -331,6 +317,7 @@ class StackObjectsLayoutFunction {
331
317
  const footerObjects = objects[0];
332
318
  const bodyObjects = objects[1];
333
319
  const bodyWidth = canvasDimensions.widthPx - canvasMargin.right - canvasMargin.left;
320
+ canvas.redraw();
334
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.
335
322
  this.applyWidths(allObjects, bodyWidth);
336
323
  const bodyRows = this.getRows(bodyObjects);
@@ -417,11 +404,7 @@ class StackObjectsLayoutFunction {
417
404
  object.setPosition({
418
405
  width: actualWidth
419
406
  });
420
- });
421
- _(objects).forEach((object) => {
422
- if (object.getType() !== exports.ObjectType.VERTICAL_RULE) {
423
- object.updateDimensions();
424
- }
407
+ object.updateDimensions();
425
408
  });
426
409
  }
427
410
  adjustCanvasSize(canvas, requiredHeight) {
@@ -494,7 +477,6 @@ class StandardLayoutManager extends AbstractLayoutManager {
494
477
  this.repositionBackgrounds = new RepositionBackgroundsLayoutFunction();
495
478
  this.stackObjects = new StackObjectsLayoutFunction();
496
479
  this.updateObjectDimensions = new UpdateObjectDimensionsLayoutFunction();
497
- this.layoutBatchScheduled = false;
498
480
  }
499
481
  /**
500
482
  * @override
@@ -536,19 +518,12 @@ class StandardLayoutManager extends AbstractLayoutManager {
536
518
  this.stackAndRedraw();
537
519
  }
538
520
  stackAndRedraw() {
539
- if (this.layoutBatchScheduled) {
540
- return;
541
- }
542
- this.layoutBatchScheduled = true;
543
- Promise.resolve().then(() => {
544
- this.stackObjects.execute(this.canvas);
545
- this.repositionCutoffs.execute(this.canvas);
546
- this.repositionBorders.execute(this.canvas);
547
- this.repositionBackgrounds.execute(this.canvas);
548
- this.updateObjectDimensions.execute(this.canvas);
549
- this.redrawAll.execute(this.canvas);
550
- this.layoutBatchScheduled = false;
551
- });
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);
552
527
  }
553
528
  }
554
529
 
@@ -1088,8 +1063,7 @@ class AbstractCanvasObject {
1088
1063
  mt: options.allowScale,
1089
1064
  mr: options.allowScale,
1090
1065
  mb: options.allowScale,
1091
- mtr: options.allowRotate,
1092
- // dragControl: options.allowMove // Show drag control when movement is allowed
1066
+ mtr: options.allowRotate
1093
1067
  });
1094
1068
  }
1095
1069
  }
@@ -1827,42 +1801,15 @@ exports.ObjectType = void 0;
1827
1801
  class Text extends AbstractCanvasObject {
1828
1802
  constructor(canvas, fabricObject, persistedField) {
1829
1803
  super(exports.ObjectType.TEXT, canvas, fabricObject, persistedField);
1830
- this.notifyCanvasTimeout = null;
1831
- this.NOTIFY_DELAY_MS = 150; // Debounce canvas notifications
1832
1804
  this.canvas = canvas;
1833
1805
  this.fontLibrary = canvas.getFontLibrary();
1834
1806
  this.baseComponentHeight = this.fabricObject.height;
1835
1807
  this.text$ = new rxjs.BehaviorSubject(this.fabricObject.text);
1836
1808
  this.data.userText = this.fabricObject.text;
1837
1809
  this.textSelection$ = new rxjs.BehaviorSubject(new TextSelection(this.fabricObject, this.fontLibrary));
1838
- this.fabricObject.set({
1839
- strokeWidth: 0,
1840
- perPixelTargetFind: false // Disable per-pixel detection for this object
1841
- });
1810
+ this.fabricObject.set({ strokeWidth: 0 });
1842
1811
  this.fabricObject.on("changed", () => this.onTextChange());
1843
1812
  this.fabricObject.on("selection:changed", () => this.onSelectionChange());
1844
- this.fabricObject.containsPoint.bind(this.fabricObject);
1845
- this.fabricObject.containsPoint = function (point) {
1846
- const coords = this.getCoords();
1847
- if (coords.length !== 4)
1848
- return false;
1849
- let inside = false;
1850
- for (let i = 0, j = coords.length - 1; i < coords.length; j = i++) {
1851
- const xi = coords[i].x, yi = coords[i].y;
1852
- const xj = coords[j].x, yj = coords[j].y;
1853
- const intersect = ((yi > point.y) !== (yj > point.y))
1854
- && (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);
1855
- if (intersect)
1856
- inside = !inside;
1857
- }
1858
- return inside;
1859
- };
1860
- this.fabricObject.on("mousedown", (e) => {
1861
- var _a;
1862
- if (!this.fabricObject.isEditing) {
1863
- (_a = e.e) === null || _a === void 0 ? void 0 : _a.preventDefault();
1864
- }
1865
- });
1866
1813
  this.applyBulletListMonkeyPatches();
1867
1814
  }
1868
1815
  /**
@@ -2472,7 +2419,6 @@ class Text extends AbstractCanvasObject {
2472
2419
  lockMovementY: true,
2473
2420
  hasControls: false,
2474
2421
  selectable: false,
2475
- // evented: false, // Don't capture mouse events - allows clicks to pass through to textbox
2476
2422
  originX: "left",
2477
2423
  originY: "top"
2478
2424
  });
@@ -2567,7 +2513,7 @@ class Text extends AbstractCanvasObject {
2567
2513
  text: `${lineNumber}.`
2568
2514
  });
2569
2515
  }
2570
- if (method === "fillText" && this.isOrderedList() && this.fabricObject.left <= 3) {
2516
+ if (method === "fillText" && this.fabricObject.left <= 3) {
2571
2517
  left = left + this.getOrderedListMargin();
2572
2518
  }
2573
2519
  this.fabricObject.dirty = true;
@@ -2579,24 +2525,13 @@ class Text extends AbstractCanvasObject {
2579
2525
  * @inheritDoc
2580
2526
  */
2581
2527
  updateFabricObjectBehavior() {
2582
- // DON'T call super.updateFabricObjectBehavior() for textboxes
2583
- // because it locks movement which interferes with text editing in Fabric v7
2584
2528
  super.updateFabricObjectBehavior();
2585
2529
  const profile = this.canvas.getProfile();
2586
2530
  const allowManualPositioning = !this.canvas.getLayoutManager().isEnforcedPositioning;
2587
- // For textboxes, we keep them moveable but hide all controls except drag control
2588
- // This allows text editing to work properly while still supporting drag via the icon
2589
2531
  this.fabricObject.set({
2590
- lockMovementX: false, // Allow dragging
2591
- lockMovementY: false, // Allow dragging
2592
2532
  lockScalingY: true,
2593
2533
  lockScalingX: this.isPinToBottom(),
2594
- lockScalingFlip: true,
2595
- lockRotation: true,
2596
- selectable: true,
2597
- editable: false, // Only enable on double-click
2598
- hoverCursor: "move", // Show move cursor by default
2599
- moveCursor: "move",
2534
+ lockScalingFlip: true
2600
2535
  });
2601
2536
  this.fabricObject.setControlsVisibility({
2602
2537
  tl: false,
@@ -2606,50 +2541,35 @@ class Text extends AbstractCanvasObject {
2606
2541
  ml: allowManualPositioning && profile.allowScale && !this.isPinToBottom(),
2607
2542
  mt: false,
2608
2543
  mr: allowManualPositioning && profile.allowScale && !this.isPinToBottom(),
2609
- mb: false,
2610
- mtr: false,
2544
+ mb: false
2611
2545
  });
2612
2546
  this.redraw();
2613
2547
  }
2614
2548
  onTextChange() {
2615
2549
  this.data.userText = this.fabricObject.text;
2616
2550
  this.text$.next(this.fabricObject.text);
2617
- // Only check width bounds if text is close to canvas edge (performance optimization)
2618
- const currentLeft = this.fabricObject.left || 0;
2619
- const canvasWidth = this.canvas.getDimensions().widthPx;
2620
- // Skip expensive getBoundingBox if object is clearly within bounds
2621
- if (currentLeft + (this.fabricObject.width || 0) > canvasWidth * 0.9) {
2622
- if (this.getBoundingBox().width > canvasWidth) {
2623
- this.fabricObject.set({ left: 0, width: canvasWidth });
2624
- this.redraw();
2625
- }
2551
+ if (this.getBoundingBox().width > this.canvas.getDimensions().widthPx) {
2552
+ this.fabricObject.set({ left: 0, width: this.canvas.getDimensions().widthPx });
2553
+ this.redraw();
2626
2554
  }
2627
2555
  }
2628
2556
  onDelete() {
2629
2557
  this.removeBullets();
2630
2558
  }
2631
2559
  onSelectionChange() {
2632
- this.updateTextSelection(false);
2560
+ this.updateTextSelection();
2633
2561
  }
2634
2562
  hasTextSelection() {
2635
2563
  return this.canvas.getSelection().isText && this.textSelection$.value.length > 0;
2636
2564
  }
2637
- updateTextSelection(notifyCanvas = true) {
2565
+ updateTextSelection() {
2638
2566
  if (this.canvas.headless)
2639
2567
  return;
2640
2568
  this.textSelection$.next(new TextSelection(this.fabricObject, this.fontLibrary));
2641
- if (notifyCanvas) {
2642
- this.notifyCanvasOfTextChanges();
2643
- }
2569
+ this.notifyCanvasOfTextChanges();
2644
2570
  }
2645
2571
  notifyCanvasOfTextChanges() {
2646
- if (this.notifyCanvasTimeout) {
2647
- clearTimeout(this.notifyCanvasTimeout);
2648
- }
2649
- this.notifyCanvasTimeout = setTimeout(() => {
2650
- this.canvas.notifyTextChanged();
2651
- this.notifyCanvasTimeout = null;
2652
- }, this.NOTIFY_DELAY_MS);
2572
+ this.canvas.notifyTextChanged();
2653
2573
  }
2654
2574
  }
2655
2575
  Text.NEW_LINE = "\n";
@@ -2815,7 +2735,6 @@ class AbstractInteractiveCanvas {
2815
2735
  this.devicePixelRatio = devicePixelRatio;
2816
2736
  this.fontLibrary = new FontLibrary();
2817
2737
  this.dimensionChangeRejections$ = new rxjs.Subject();
2818
- this.redrawScheduled = false;
2819
2738
  this.IMAGE_CAPTURE_COEFFICIENT = this.calculateImageCaptureCoefficient();
2820
2739
  /**
2821
2740
  * Fabric defaults to only 2 decimal places when saving, which causes havoc on the scaling of large images.
@@ -2838,7 +2757,7 @@ class AbstractInteractiveCanvas {
2838
2757
  this.fabricCanvas.stateful = true;
2839
2758
  this.fabricCanvas.preserveObjectStacking = true;
2840
2759
  this.fabricCanvas.perPixelTargetFind = true;
2841
- this.fabricCanvas.targetFindTolerance = 4; // Reduced tolerance for more precise text selection
2760
+ this.fabricCanvas.targetFindTolerance = 4;
2842
2761
  this.setBackgroundColor("#FFFFFF");
2843
2762
  }
2844
2763
  /**
@@ -3142,6 +3061,7 @@ class AbstractInteractiveCanvas {
3142
3061
  */
3143
3062
  addImageObject(imageUrl) {
3144
3063
  return __awaiter(this, void 0, void 0, function* () {
3064
+ // Don't let the image be more than half the canvas in size.
3145
3065
  const maxSizeScale = 2;
3146
3066
  const maxWidth = this.dimensions$.value.widthPx / maxSizeScale;
3147
3067
  const options = { crossOrigin: "anonymous" };
@@ -3153,6 +3073,7 @@ class AbstractInteractiveCanvas {
3153
3073
  if (curWidth > maxWidth) {
3154
3074
  newScale = maxWidth / curWidth;
3155
3075
  }
3076
+ // Set image properties and add to the canvas
3156
3077
  const fabricImage = new this.fabric.Image(image, {
3157
3078
  id: id,
3158
3079
  scaleX: newScale,
@@ -3172,6 +3093,7 @@ class AbstractInteractiveCanvas {
3172
3093
  return object;
3173
3094
  }
3174
3095
  else {
3096
+ // Browser-side logic
3175
3097
  return yield new Promise((resolve) => {
3176
3098
  this.fabric.Image.fromURL(imageUrl, options).then((fabricImage) => {
3177
3099
  const id = this.randomId();
@@ -3223,13 +3145,14 @@ class AbstractInteractiveCanvas {
3223
3145
  fontFamily: profile.defaultFont,
3224
3146
  textAlign: profile.defaultTextAlignment,
3225
3147
  hoverCursor: "move",
3226
- moveCursor: "move",
3227
- editable: false,
3228
- editingBorderColor: '#2196F3',
3148
+ moveCursor: 'move',
3229
3149
  multiLine: true,
3230
3150
  lineHeight: profile.defaultLineHeight,
3231
3151
  charSpacing: profile.defaultCharacterSpacing,
3152
+ perPixelTargetFind: false,
3153
+ targetFindTolerance: 4,
3232
3154
  splitByGrapheme: true,
3155
+ editable: false, // Only enable on double-click
3233
3156
  padding: 0,
3234
3157
  cornerSize: 8,
3235
3158
  touchCornerSize: 12
@@ -3679,17 +3602,9 @@ class AbstractInteractiveCanvas {
3679
3602
  }
3680
3603
  /**
3681
3604
  * Redraw the canvas.
3682
- * Uses requestAnimationFrame to batch multiple redraw calls into a single render.
3683
3605
  */
3684
3606
  redraw() {
3685
- if (this.redrawScheduled) {
3686
- return;
3687
- }
3688
- this.redrawScheduled = true;
3689
- requestAnimationFrame(() => {
3690
- this.fabricCanvas.requestRenderAll();
3691
- this.redrawScheduled = false;
3692
- });
3607
+ this.fabricCanvas.requestRenderAll();
3693
3608
  }
3694
3609
  syncObjectList() {
3695
3610
  const newObjectList = _(this.fabricCanvas.getObjects())
@@ -3815,6 +3730,7 @@ class AbstractInteractiveCanvas {
3815
3730
  * Update the actual dimensions of the canvas by multiplying the requested dimensions by the current zoom factor.
3816
3731
  */
3817
3732
  updateCanvasDimensions(dims, zoom) {
3733
+ this.fabricCanvas.getWidth();
3818
3734
  const width = dims.widthPx * zoom;
3819
3735
  const height = dims.heightPx * zoom;
3820
3736
  this.fabricCanvas.setZoom(zoom);
@@ -3822,6 +3738,37 @@ class AbstractInteractiveCanvas {
3822
3738
  width,
3823
3739
  height
3824
3740
  });
3741
+ /* if (this.layoutManager$.value instanceof AdvancedLayoutManager) {
3742
+ const scaleFactor = width / oldWidth;
3743
+ this.fabricCanvas.calcOffset();
3744
+ const objects = this.fabricCanvas.getObjects();
3745
+ objects.forEach((obj: any) => {
3746
+ obj.set({
3747
+ left: obj.left * scaleFactor,
3748
+ });
3749
+ if (obj.type === "textbox" || obj.type === "text") {
3750
+ obj.set({
3751
+ scaleX: 1,
3752
+ scaleY: 1,
3753
+ width: obj.width * scaleFactor,
3754
+ });
3755
+ obj.initDimensions();
3756
+ } else if (obj.type === "border" || obj.type === "rect") {
3757
+ obj.set({
3758
+ width: obj.width * scaleFactor,
3759
+ scaleX: 1,
3760
+ strokeUniform: true,
3761
+ strokeAlign: "inner",
3762
+ });
3763
+ } else {
3764
+ obj.set({
3765
+ scaleX: obj.scaleX * scaleFactor,
3766
+ });
3767
+ }
3768
+ obj.setCoords();
3769
+ });
3770
+ this.fabricCanvas.requestRenderAll();
3771
+ }*/
3825
3772
  }
3826
3773
  onProfileChanged(profile) {
3827
3774
  this.setMultiSelect(profile.allowMultiSelect);
@@ -3976,6 +3923,7 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
3976
3923
  this.fabricCanvas.on("selection:created", (event) => this.onFabricSelectionChanged(event));
3977
3924
  this.fabricCanvas.on("selection:cleared", (event) => this.onFabricSelectionChanged(event));
3978
3925
  this.fabricCanvas.on("text:editing:entered", () => this.onTextEditing());
3926
+ this.fabricCanvas.on("text:editing:exited", () => this.onTextEditing());
3979
3927
  this.fabricCanvas.on("text:editing:exited", (event) => {
3980
3928
  this.onTextEditing();
3981
3929
  const target = event.target;
@@ -3984,6 +3932,8 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
3984
3932
  }
3985
3933
  });
3986
3934
  this.fabricCanvas.on("text:changed", () => this.onTextChange());
3935
+ // Handle double-click on textbox to enter editing mode
3936
+ // @ts-ignore
3987
3937
  this.fabricCanvas.on("mouse:dblclick", (event) => this.onDoubleClick(event));
3988
3938
  }
3989
3939
  /**
@@ -4018,20 +3968,29 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
4018
3968
  selection.setEvent(event);
4019
3969
  }
4020
3970
  this.selection$.next(selection);
3971
+ this.enterTextSelectionModeIfRequired();
3972
+ }
3973
+ enterTextSelectionModeIfRequired() {
3974
+ var _a, _b;
3975
+ if (!this.profile$.value.enterTextEditImmediatelyOnSelect && !this.layoutManager$.value.enterTextEditImmediatelyOnSelect)
3976
+ return;
3977
+ if (((_b = (_a = this.selection$.value) === null || _a === void 0 ? void 0 : _a.objects[0]) === null || _b === void 0 ? void 0 : _b.getType()) !== exports.ObjectType.TEXT)
3978
+ return;
3979
+ const text = this.selection$.value.objects[0];
3980
+ text.enterEditing();
4021
3981
  }
4022
3982
  onTextEditing() {
4023
3983
  const selected = this.getFieldsByCanvasObject(this.fabricCanvas.getActiveObjects());
4024
3984
  this.selection$.next(new SelectionData(selected));
4025
3985
  }
4026
3986
  onDoubleClick(event) {
4027
- var _a, _b;
4028
3987
  const target = event.target;
4029
3988
  if (target && (target.type === 'textbox' || target.type === 'i-text')) {
4030
- const targetIText = target;
4031
- if (!targetIText.isEditing) {
3989
+ if (!target.isEditing) {
4032
3990
  target.set({ editable: true, hoverCursor: 'text' });
4033
- (_a = targetIText.enterEditing) === null || _a === void 0 ? void 0 : _a.call(targetIText);
4034
- (_b = targetIText.hiddenTextarea) === null || _b === void 0 ? void 0 : _b.focus();
3991
+ // target.enterEditing();
3992
+ // target.hiddenTextarea?.focus();
3993
+ this.enterTextSelectionModeIfRequired();
4035
3994
  }
4036
3995
  }
4037
3996
  }
@@ -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-11",
3
+ "version": "2.0.25-beta-13",
4
4
  "description": "Leapfrog interactive canvas",
5
5
  "files": [
6
6
  "dist"