@leapfrog/interactive-canvas 2.0.25-beta-10 → 2.0.25-beta-12

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.
@@ -343,7 +343,6 @@ export declare abstract class AbstractInteractiveCanvas implements IInteractiveC
343
343
  private redrawScheduled;
344
344
  /**
345
345
  * Redraw the canvas.
346
- * Uses requestAnimationFrame to batch multiple redraw calls into a single render.
347
346
  */
348
347
  redraw(): void;
349
348
  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
  }
@@ -310,6 +310,7 @@ class StackObjectsLayoutFunction {
310
310
  const footerObjects = objects[0];
311
311
  const bodyObjects = objects[1];
312
312
  const bodyWidth = canvasDimensions.widthPx - canvasMargin.right - canvasMargin.left;
313
+ // canvas.redraw();
313
314
  //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
315
  this.applyWidths(allObjects, bodyWidth);
315
316
  const bodyRows = this.getRows(bodyObjects);
@@ -396,11 +397,12 @@ class StackObjectsLayoutFunction {
396
397
  object.setPosition({
397
398
  width: actualWidth
398
399
  });
399
- });
400
- _(objects).forEach((object) => {
401
- if (object.getType() !== ObjectType.VERTICAL_RULE) {
402
- object.updateDimensions();
403
- }
400
+ //object.updateDimensions();
401
+ _(objects).forEach((object) => {
402
+ if (object.getType() !== ObjectType.VERTICAL_RULE) {
403
+ object.updateDimensions();
404
+ }
405
+ });
404
406
  });
405
407
  }
406
408
  adjustCanvasSize(canvas, requiredHeight) {
@@ -1067,8 +1069,7 @@ class AbstractCanvasObject {
1067
1069
  mt: options.allowScale,
1068
1070
  mr: options.allowScale,
1069
1071
  mb: options.allowScale,
1070
- mtr: options.allowRotate,
1071
- // dragControl: options.allowMove // Show drag control when movement is allowed
1072
+ mtr: options.allowRotate
1072
1073
  });
1073
1074
  }
1074
1075
  }
@@ -1807,13 +1808,14 @@ class Text extends AbstractCanvasObject {
1807
1808
  constructor(canvas, fabricObject, persistedField) {
1808
1809
  super(ObjectType.TEXT, canvas, fabricObject, persistedField);
1809
1810
  this.notifyCanvasTimeout = null;
1810
- this.NOTIFY_DELAY_MS = 150; // Debounce canvas notifications
1811
+ this.NOTIFY_DELAY_MS = 150;
1811
1812
  this.canvas = canvas;
1812
1813
  this.fontLibrary = canvas.getFontLibrary();
1813
1814
  this.baseComponentHeight = this.fabricObject.height;
1814
1815
  this.text$ = new BehaviorSubject(this.fabricObject.text);
1815
1816
  this.data.userText = this.fabricObject.text;
1816
1817
  this.textSelection$ = new BehaviorSubject(new TextSelection(this.fabricObject, this.fontLibrary));
1818
+ //this.fabricObject.set({strokeWidth: 0});
1817
1819
  this.fabricObject.set({
1818
1820
  strokeWidth: 0,
1819
1821
  perPixelTargetFind: false // Disable per-pixel detection for this object
@@ -2190,27 +2192,87 @@ class Text extends AbstractCanvasObject {
2190
2192
  * @override
2191
2193
  * @inheritDoc
2192
2194
  */
2193
- getBulletMargin() {
2194
- if (!this.isBulleted())
2195
- return 0;
2195
+ /*public getBulletMargin(): number {
2196
+ if (!this.isBulleted()) return 0;
2197
+
2196
2198
  let fontSize = 0;
2197
- each(this.fabricObject._textLines, (textLine, lineIndex) => {
2199
+ each(this.fabricObject._textLines, (textLine: string[], lineIndex: number) => {
2198
2200
  fontSize = Math.max(fontSize, this.fabricObject.getValueOfPropertyAt(lineIndex, 0, "fontSize"));
2199
2201
  });
2202
+
2200
2203
  return fontSize * 0.6;
2204
+ }*/
2205
+ getBulletMargin() {
2206
+ if (!this.isBulleted())
2207
+ return 0;
2208
+ try {
2209
+ let fontSize = 0;
2210
+ each(this.fabricObject._textLines, (textLine, lineIndex) => {
2211
+ const size = this.fabricObject.getValueOfPropertyAt(lineIndex, 0, "fontSize");
2212
+ // Sanity check fontSize
2213
+ if (isFinite(size) && size > 0 && size < 1000) {
2214
+ fontSize = Math.max(fontSize, size);
2215
+ }
2216
+ });
2217
+ // Additional safety check
2218
+ if (!isFinite(fontSize) || fontSize < 0 || fontSize > 1000) {
2219
+ return 0;
2220
+ }
2221
+ return fontSize * 0.6;
2222
+ }
2223
+ catch (e) {
2224
+ console.warn('Error calculating bullet margin:', e);
2225
+ return 0;
2226
+ }
2201
2227
  }
2202
2228
  /**
2203
2229
  * @override
2204
2230
  * @inheritDoc
2205
2231
  */
2232
+ /*public getOrderedListMargin(): number {
2233
+ if (!this.isOrderedList()) return 0;
2234
+
2235
+ const orderedListMarkerEntities = this.fabricObject._orderedListMarkerEntities;
2236
+ let maxValue = max(
2237
+ (orderedListMarkerEntities ?? [])
2238
+ .filter((x: Textbox) => !!x)
2239
+ .map((x: fabric.Textbox) => x.getBoundingRect().width)
2240
+ );
2241
+ return maxValue ? (maxValue * 1.2) : 0;
2242
+ }*/
2206
2243
  getOrderedListMargin() {
2207
2244
  if (!this.isOrderedList())
2208
2245
  return 0;
2209
2246
  const orderedListMarkerEntities = this.fabricObject._orderedListMarkerEntities;
2210
- let maxValue = max((orderedListMarkerEntities !== null && orderedListMarkerEntities !== void 0 ? orderedListMarkerEntities : [])
2211
- .filter((x) => !!x)
2212
- .map((x) => x.getBoundingRect().width));
2213
- return maxValue ? (maxValue * 1.2) : 0;
2247
+ try {
2248
+ const widths = (orderedListMarkerEntities !== null && orderedListMarkerEntities !== void 0 ? orderedListMarkerEntities : [])
2249
+ .filter((x) => !!x)
2250
+ .map((x) => {
2251
+ var _a;
2252
+ try {
2253
+ const rect = x.getBoundingRect();
2254
+ const width = (_a = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _a !== void 0 ? _a : 0;
2255
+ // Sanity check: if width is invalid, return 0
2256
+ if (!isFinite(width) || width < 0 || width > 1000) {
2257
+ return 0;
2258
+ }
2259
+ return width;
2260
+ }
2261
+ catch (e) {
2262
+ return 0;
2263
+ }
2264
+ });
2265
+ const maxValue = max(widths);
2266
+ // Additional safety: ensure maxValue is a valid number
2267
+ if (!maxValue || !isFinite(maxValue) || maxValue < 0 || maxValue > 1000) {
2268
+ return 0;
2269
+ }
2270
+ return maxValue * 1.2;
2271
+ }
2272
+ catch (e) {
2273
+ console.warn('Error calculating ordered list margin:', e);
2274
+ return 0;
2275
+ }
2214
2276
  }
2215
2277
  /**
2216
2278
  * @override
@@ -2451,7 +2513,6 @@ class Text extends AbstractCanvasObject {
2451
2513
  lockMovementY: true,
2452
2514
  hasControls: false,
2453
2515
  selectable: false,
2454
- // evented: false, // Don't capture mouse events - allows clicks to pass through to textbox
2455
2516
  originX: "left",
2456
2517
  originY: "top"
2457
2518
  });
@@ -2546,7 +2607,10 @@ class Text extends AbstractCanvasObject {
2546
2607
  text: `${lineNumber}.`
2547
2608
  });
2548
2609
  }
2549
- if (method === "fillText" && this.fabricObject.left <= 3) {
2610
+ /* if (method === "fillText" && this.fabricObject.left <= 3) {
2611
+ left = left + this.getOrderedListMargin();
2612
+ }*/
2613
+ if (method === "fillText" && this.isOrderedList() && this.fabricObject.left <= 3) {
2550
2614
  left = left + this.getOrderedListMargin();
2551
2615
  }
2552
2616
  this.fabricObject.dirty = true;
@@ -2558,16 +2622,12 @@ class Text extends AbstractCanvasObject {
2558
2622
  * @inheritDoc
2559
2623
  */
2560
2624
  updateFabricObjectBehavior() {
2561
- // DON'T call super.updateFabricObjectBehavior() for textboxes
2562
- // because it locks movement which interferes with text editing in Fabric v7
2563
2625
  super.updateFabricObjectBehavior();
2564
2626
  const profile = this.canvas.getProfile();
2565
2627
  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
2628
  this.fabricObject.set({
2569
- lockMovementX: false, // Allow dragging
2570
- lockMovementY: false, // Allow dragging
2629
+ lockMovementX: false,
2630
+ lockMovementY: false,
2571
2631
  lockScalingY: true,
2572
2632
  lockScalingX: this.isPinToBottom(),
2573
2633
  lockScalingFlip: true,
@@ -2586,14 +2646,13 @@ class Text extends AbstractCanvasObject {
2586
2646
  mt: false,
2587
2647
  mr: allowManualPositioning && profile.allowScale && !this.isPinToBottom(),
2588
2648
  mb: false,
2589
- mtr: false,
2649
+ mtr: false
2590
2650
  });
2591
2651
  this.redraw();
2592
2652
  }
2593
2653
  onTextChange() {
2594
2654
  this.data.userText = this.fabricObject.text;
2595
2655
  this.text$.next(this.fabricObject.text);
2596
- // Only check width bounds if text is close to canvas edge (performance optimization)
2597
2656
  const currentLeft = this.fabricObject.left || 0;
2598
2657
  const canvasWidth = this.canvas.getDimensions().widthPx;
2599
2658
  // Skip expensive getBoundingBox if object is clearly within bounds
@@ -2817,7 +2876,7 @@ class AbstractInteractiveCanvas {
2817
2876
  this.fabricCanvas.stateful = true;
2818
2877
  this.fabricCanvas.preserveObjectStacking = true;
2819
2878
  this.fabricCanvas.perPixelTargetFind = true;
2820
- this.fabricCanvas.targetFindTolerance = 4; // Reduced tolerance for more precise text selection
2879
+ this.fabricCanvas.targetFindTolerance = 4;
2821
2880
  this.setBackgroundColor("#FFFFFF");
2822
2881
  }
2823
2882
  /**
@@ -3121,6 +3180,7 @@ class AbstractInteractiveCanvas {
3121
3180
  */
3122
3181
  addImageObject(imageUrl) {
3123
3182
  return __awaiter(this, void 0, void 0, function* () {
3183
+ // Don't let the image be more than half the canvas in size.
3124
3184
  const maxSizeScale = 2;
3125
3185
  const maxWidth = this.dimensions$.value.widthPx / maxSizeScale;
3126
3186
  const options = { crossOrigin: "anonymous" };
@@ -3132,6 +3192,7 @@ class AbstractInteractiveCanvas {
3132
3192
  if (curWidth > maxWidth) {
3133
3193
  newScale = maxWidth / curWidth;
3134
3194
  }
3195
+ // Set image properties and add to the canvas
3135
3196
  const fabricImage = new this.fabric.Image(image, {
3136
3197
  id: id,
3137
3198
  scaleX: newScale,
@@ -3151,6 +3212,7 @@ class AbstractInteractiveCanvas {
3151
3212
  return object;
3152
3213
  }
3153
3214
  else {
3215
+ // Browser-side logic
3154
3216
  return yield new Promise((resolve) => {
3155
3217
  this.fabric.Image.fromURL(imageUrl, options).then((fabricImage) => {
3156
3218
  const id = this.randomId();
@@ -3202,13 +3264,15 @@ class AbstractInteractiveCanvas {
3202
3264
  fontFamily: profile.defaultFont,
3203
3265
  textAlign: profile.defaultTextAlignment,
3204
3266
  hoverCursor: "move",
3205
- moveCursor: "move",
3206
- editable: false,
3207
- editingBorderColor: '#2196F3',
3267
+ moveCursor: 'move',
3208
3268
  multiLine: true,
3209
3269
  lineHeight: profile.defaultLineHeight,
3210
3270
  charSpacing: profile.defaultCharacterSpacing,
3271
+ perPixelTargetFind: false,
3272
+ targetFindTolerance: 4,
3211
3273
  splitByGrapheme: true,
3274
+ editable: false, // Only enable on double-click
3275
+ editingBorderColor: '#2196F3',
3212
3276
  padding: 0,
3213
3277
  cornerSize: 8,
3214
3278
  touchCornerSize: 12
@@ -3658,7 +3722,6 @@ class AbstractInteractiveCanvas {
3658
3722
  }
3659
3723
  /**
3660
3724
  * Redraw the canvas.
3661
- * Uses requestAnimationFrame to batch multiple redraw calls into a single render.
3662
3725
  */
3663
3726
  redraw() {
3664
3727
  if (this.redrawScheduled) {
@@ -3794,6 +3857,7 @@ class AbstractInteractiveCanvas {
3794
3857
  * Update the actual dimensions of the canvas by multiplying the requested dimensions by the current zoom factor.
3795
3858
  */
3796
3859
  updateCanvasDimensions(dims, zoom) {
3860
+ const oldWidth = this.fabricCanvas.getWidth();
3797
3861
  const width = dims.widthPx * zoom;
3798
3862
  const height = dims.heightPx * zoom;
3799
3863
  this.fabricCanvas.setZoom(zoom);
@@ -3801,6 +3865,34 @@ class AbstractInteractiveCanvas {
3801
3865
  width,
3802
3866
  height
3803
3867
  });
3868
+ const scaleFactor = width / oldWidth;
3869
+ const objects = this.fabricCanvas.getObjects();
3870
+ objects.forEach((obj) => {
3871
+ obj.set({
3872
+ left: obj.left * scaleFactor,
3873
+ });
3874
+ if (obj.type === "textbox" || obj.type === "text") {
3875
+ obj.set({
3876
+ scaleX: 1,
3877
+ scaleY: 1,
3878
+ width: obj.width * scaleFactor,
3879
+ });
3880
+ }
3881
+ else if (obj.type === "border" || obj.type === "rect") {
3882
+ obj.set({
3883
+ width: obj.width * scaleFactor,
3884
+ scaleX: 1,
3885
+ strokeUniform: true,
3886
+ });
3887
+ }
3888
+ else {
3889
+ obj.set({
3890
+ scaleX: obj.scaleX * scaleFactor,
3891
+ });
3892
+ }
3893
+ obj.setCoords();
3894
+ });
3895
+ this.fabricCanvas.requestRenderAll();
3804
3896
  }
3805
3897
  onProfileChanged(profile) {
3806
3898
  this.setMultiSelect(profile.allowMultiSelect);
@@ -3955,6 +4047,7 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
3955
4047
  this.fabricCanvas.on("selection:created", (event) => this.onFabricSelectionChanged(event));
3956
4048
  this.fabricCanvas.on("selection:cleared", (event) => this.onFabricSelectionChanged(event));
3957
4049
  this.fabricCanvas.on("text:editing:entered", () => this.onTextEditing());
4050
+ //this.fabricCanvas.on("text:editing:exited", () => this.onTextEditing());
3958
4051
  this.fabricCanvas.on("text:editing:exited", (event) => {
3959
4052
  this.onTextEditing();
3960
4053
  const target = event.target;
@@ -3963,6 +4056,8 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
3963
4056
  }
3964
4057
  });
3965
4058
  this.fabricCanvas.on("text:changed", () => this.onTextChange());
4059
+ // Handle double-click on textbox to enter editing mode
4060
+ // @ts-ignore
3966
4061
  this.fabricCanvas.on("mouse:dblclick", (event) => this.onDoubleClick(event));
3967
4062
  }
3968
4063
  /**
@@ -3997,20 +4092,29 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
3997
4092
  selection.setEvent(event);
3998
4093
  }
3999
4094
  this.selection$.next(selection);
4095
+ this.enterTextSelectionModeIfRequired();
4096
+ }
4097
+ enterTextSelectionModeIfRequired() {
4098
+ var _a, _b;
4099
+ if (!this.profile$.value.enterTextEditImmediatelyOnSelect && !this.layoutManager$.value.enterTextEditImmediatelyOnSelect)
4100
+ return;
4101
+ 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)
4102
+ return;
4103
+ const text = this.selection$.value.objects[0];
4104
+ text.enterEditing();
4000
4105
  }
4001
4106
  onTextEditing() {
4002
4107
  const selected = this.getFieldsByCanvasObject(this.fabricCanvas.getActiveObjects());
4003
4108
  this.selection$.next(new SelectionData(selected));
4004
4109
  }
4005
4110
  onDoubleClick(event) {
4006
- var _a, _b;
4007
4111
  const target = event.target;
4008
4112
  if (target && (target.type === 'textbox' || target.type === 'i-text')) {
4009
- const targetIText = target;
4010
- if (!targetIText.isEditing) {
4113
+ if (!target.isEditing) {
4011
4114
  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();
4115
+ // target.enterEditing();
4116
+ // target.hiddenTextarea?.focus();
4117
+ this.enterTextSelectionModeIfRequired();
4014
4118
  }
4015
4119
  }
4016
4120
  }
@@ -331,6 +331,7 @@ class StackObjectsLayoutFunction {
331
331
  const footerObjects = objects[0];
332
332
  const bodyObjects = objects[1];
333
333
  const bodyWidth = canvasDimensions.widthPx - canvasMargin.right - canvasMargin.left;
334
+ // canvas.redraw();
334
335
  //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
336
  this.applyWidths(allObjects, bodyWidth);
336
337
  const bodyRows = this.getRows(bodyObjects);
@@ -417,11 +418,12 @@ class StackObjectsLayoutFunction {
417
418
  object.setPosition({
418
419
  width: actualWidth
419
420
  });
420
- });
421
- _(objects).forEach((object) => {
422
- if (object.getType() !== exports.ObjectType.VERTICAL_RULE) {
423
- object.updateDimensions();
424
- }
421
+ //object.updateDimensions();
422
+ _(objects).forEach((object) => {
423
+ if (object.getType() !== exports.ObjectType.VERTICAL_RULE) {
424
+ object.updateDimensions();
425
+ }
426
+ });
425
427
  });
426
428
  }
427
429
  adjustCanvasSize(canvas, requiredHeight) {
@@ -1088,8 +1090,7 @@ class AbstractCanvasObject {
1088
1090
  mt: options.allowScale,
1089
1091
  mr: options.allowScale,
1090
1092
  mb: options.allowScale,
1091
- mtr: options.allowRotate,
1092
- // dragControl: options.allowMove // Show drag control when movement is allowed
1093
+ mtr: options.allowRotate
1093
1094
  });
1094
1095
  }
1095
1096
  }
@@ -1828,13 +1829,14 @@ class Text extends AbstractCanvasObject {
1828
1829
  constructor(canvas, fabricObject, persistedField) {
1829
1830
  super(exports.ObjectType.TEXT, canvas, fabricObject, persistedField);
1830
1831
  this.notifyCanvasTimeout = null;
1831
- this.NOTIFY_DELAY_MS = 150; // Debounce canvas notifications
1832
+ this.NOTIFY_DELAY_MS = 150;
1832
1833
  this.canvas = canvas;
1833
1834
  this.fontLibrary = canvas.getFontLibrary();
1834
1835
  this.baseComponentHeight = this.fabricObject.height;
1835
1836
  this.text$ = new rxjs.BehaviorSubject(this.fabricObject.text);
1836
1837
  this.data.userText = this.fabricObject.text;
1837
1838
  this.textSelection$ = new rxjs.BehaviorSubject(new TextSelection(this.fabricObject, this.fontLibrary));
1839
+ //this.fabricObject.set({strokeWidth: 0});
1838
1840
  this.fabricObject.set({
1839
1841
  strokeWidth: 0,
1840
1842
  perPixelTargetFind: false // Disable per-pixel detection for this object
@@ -2211,27 +2213,87 @@ class Text extends AbstractCanvasObject {
2211
2213
  * @override
2212
2214
  * @inheritDoc
2213
2215
  */
2214
- getBulletMargin() {
2215
- if (!this.isBulleted())
2216
- return 0;
2216
+ /*public getBulletMargin(): number {
2217
+ if (!this.isBulleted()) return 0;
2218
+
2217
2219
  let fontSize = 0;
2218
- _.each(this.fabricObject._textLines, (textLine, lineIndex) => {
2220
+ each(this.fabricObject._textLines, (textLine: string[], lineIndex: number) => {
2219
2221
  fontSize = Math.max(fontSize, this.fabricObject.getValueOfPropertyAt(lineIndex, 0, "fontSize"));
2220
2222
  });
2223
+
2221
2224
  return fontSize * 0.6;
2225
+ }*/
2226
+ getBulletMargin() {
2227
+ if (!this.isBulleted())
2228
+ return 0;
2229
+ try {
2230
+ let fontSize = 0;
2231
+ _.each(this.fabricObject._textLines, (textLine, lineIndex) => {
2232
+ const size = this.fabricObject.getValueOfPropertyAt(lineIndex, 0, "fontSize");
2233
+ // Sanity check fontSize
2234
+ if (isFinite(size) && size > 0 && size < 1000) {
2235
+ fontSize = Math.max(fontSize, size);
2236
+ }
2237
+ });
2238
+ // Additional safety check
2239
+ if (!isFinite(fontSize) || fontSize < 0 || fontSize > 1000) {
2240
+ return 0;
2241
+ }
2242
+ return fontSize * 0.6;
2243
+ }
2244
+ catch (e) {
2245
+ console.warn('Error calculating bullet margin:', e);
2246
+ return 0;
2247
+ }
2222
2248
  }
2223
2249
  /**
2224
2250
  * @override
2225
2251
  * @inheritDoc
2226
2252
  */
2253
+ /*public getOrderedListMargin(): number {
2254
+ if (!this.isOrderedList()) return 0;
2255
+
2256
+ const orderedListMarkerEntities = this.fabricObject._orderedListMarkerEntities;
2257
+ let maxValue = max(
2258
+ (orderedListMarkerEntities ?? [])
2259
+ .filter((x: Textbox) => !!x)
2260
+ .map((x: fabric.Textbox) => x.getBoundingRect().width)
2261
+ );
2262
+ return maxValue ? (maxValue * 1.2) : 0;
2263
+ }*/
2227
2264
  getOrderedListMargin() {
2228
2265
  if (!this.isOrderedList())
2229
2266
  return 0;
2230
2267
  const orderedListMarkerEntities = this.fabricObject._orderedListMarkerEntities;
2231
- let maxValue = _.max((orderedListMarkerEntities !== null && orderedListMarkerEntities !== void 0 ? orderedListMarkerEntities : [])
2232
- .filter((x) => !!x)
2233
- .map((x) => x.getBoundingRect().width));
2234
- return maxValue ? (maxValue * 1.2) : 0;
2268
+ try {
2269
+ const widths = (orderedListMarkerEntities !== null && orderedListMarkerEntities !== void 0 ? orderedListMarkerEntities : [])
2270
+ .filter((x) => !!x)
2271
+ .map((x) => {
2272
+ var _a;
2273
+ try {
2274
+ const rect = x.getBoundingRect();
2275
+ const width = (_a = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _a !== void 0 ? _a : 0;
2276
+ // Sanity check: if width is invalid, return 0
2277
+ if (!isFinite(width) || width < 0 || width > 1000) {
2278
+ return 0;
2279
+ }
2280
+ return width;
2281
+ }
2282
+ catch (e) {
2283
+ return 0;
2284
+ }
2285
+ });
2286
+ const maxValue = _.max(widths);
2287
+ // Additional safety: ensure maxValue is a valid number
2288
+ if (!maxValue || !isFinite(maxValue) || maxValue < 0 || maxValue > 1000) {
2289
+ return 0;
2290
+ }
2291
+ return maxValue * 1.2;
2292
+ }
2293
+ catch (e) {
2294
+ console.warn('Error calculating ordered list margin:', e);
2295
+ return 0;
2296
+ }
2235
2297
  }
2236
2298
  /**
2237
2299
  * @override
@@ -2472,7 +2534,6 @@ class Text extends AbstractCanvasObject {
2472
2534
  lockMovementY: true,
2473
2535
  hasControls: false,
2474
2536
  selectable: false,
2475
- // evented: false, // Don't capture mouse events - allows clicks to pass through to textbox
2476
2537
  originX: "left",
2477
2538
  originY: "top"
2478
2539
  });
@@ -2567,7 +2628,10 @@ class Text extends AbstractCanvasObject {
2567
2628
  text: `${lineNumber}.`
2568
2629
  });
2569
2630
  }
2570
- if (method === "fillText" && this.fabricObject.left <= 3) {
2631
+ /* if (method === "fillText" && this.fabricObject.left <= 3) {
2632
+ left = left + this.getOrderedListMargin();
2633
+ }*/
2634
+ if (method === "fillText" && this.isOrderedList() && this.fabricObject.left <= 3) {
2571
2635
  left = left + this.getOrderedListMargin();
2572
2636
  }
2573
2637
  this.fabricObject.dirty = true;
@@ -2579,16 +2643,12 @@ class Text extends AbstractCanvasObject {
2579
2643
  * @inheritDoc
2580
2644
  */
2581
2645
  updateFabricObjectBehavior() {
2582
- // DON'T call super.updateFabricObjectBehavior() for textboxes
2583
- // because it locks movement which interferes with text editing in Fabric v7
2584
2646
  super.updateFabricObjectBehavior();
2585
2647
  const profile = this.canvas.getProfile();
2586
2648
  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
2649
  this.fabricObject.set({
2590
- lockMovementX: false, // Allow dragging
2591
- lockMovementY: false, // Allow dragging
2650
+ lockMovementX: false,
2651
+ lockMovementY: false,
2592
2652
  lockScalingY: true,
2593
2653
  lockScalingX: this.isPinToBottom(),
2594
2654
  lockScalingFlip: true,
@@ -2607,14 +2667,13 @@ class Text extends AbstractCanvasObject {
2607
2667
  mt: false,
2608
2668
  mr: allowManualPositioning && profile.allowScale && !this.isPinToBottom(),
2609
2669
  mb: false,
2610
- mtr: false,
2670
+ mtr: false
2611
2671
  });
2612
2672
  this.redraw();
2613
2673
  }
2614
2674
  onTextChange() {
2615
2675
  this.data.userText = this.fabricObject.text;
2616
2676
  this.text$.next(this.fabricObject.text);
2617
- // Only check width bounds if text is close to canvas edge (performance optimization)
2618
2677
  const currentLeft = this.fabricObject.left || 0;
2619
2678
  const canvasWidth = this.canvas.getDimensions().widthPx;
2620
2679
  // Skip expensive getBoundingBox if object is clearly within bounds
@@ -2838,7 +2897,7 @@ class AbstractInteractiveCanvas {
2838
2897
  this.fabricCanvas.stateful = true;
2839
2898
  this.fabricCanvas.preserveObjectStacking = true;
2840
2899
  this.fabricCanvas.perPixelTargetFind = true;
2841
- this.fabricCanvas.targetFindTolerance = 4; // Reduced tolerance for more precise text selection
2900
+ this.fabricCanvas.targetFindTolerance = 4;
2842
2901
  this.setBackgroundColor("#FFFFFF");
2843
2902
  }
2844
2903
  /**
@@ -3142,6 +3201,7 @@ class AbstractInteractiveCanvas {
3142
3201
  */
3143
3202
  addImageObject(imageUrl) {
3144
3203
  return __awaiter(this, void 0, void 0, function* () {
3204
+ // Don't let the image be more than half the canvas in size.
3145
3205
  const maxSizeScale = 2;
3146
3206
  const maxWidth = this.dimensions$.value.widthPx / maxSizeScale;
3147
3207
  const options = { crossOrigin: "anonymous" };
@@ -3153,6 +3213,7 @@ class AbstractInteractiveCanvas {
3153
3213
  if (curWidth > maxWidth) {
3154
3214
  newScale = maxWidth / curWidth;
3155
3215
  }
3216
+ // Set image properties and add to the canvas
3156
3217
  const fabricImage = new this.fabric.Image(image, {
3157
3218
  id: id,
3158
3219
  scaleX: newScale,
@@ -3172,6 +3233,7 @@ class AbstractInteractiveCanvas {
3172
3233
  return object;
3173
3234
  }
3174
3235
  else {
3236
+ // Browser-side logic
3175
3237
  return yield new Promise((resolve) => {
3176
3238
  this.fabric.Image.fromURL(imageUrl, options).then((fabricImage) => {
3177
3239
  const id = this.randomId();
@@ -3223,13 +3285,15 @@ class AbstractInteractiveCanvas {
3223
3285
  fontFamily: profile.defaultFont,
3224
3286
  textAlign: profile.defaultTextAlignment,
3225
3287
  hoverCursor: "move",
3226
- moveCursor: "move",
3227
- editable: false,
3228
- editingBorderColor: '#2196F3',
3288
+ moveCursor: 'move',
3229
3289
  multiLine: true,
3230
3290
  lineHeight: profile.defaultLineHeight,
3231
3291
  charSpacing: profile.defaultCharacterSpacing,
3292
+ perPixelTargetFind: false,
3293
+ targetFindTolerance: 4,
3232
3294
  splitByGrapheme: true,
3295
+ editable: false, // Only enable on double-click
3296
+ editingBorderColor: '#2196F3',
3233
3297
  padding: 0,
3234
3298
  cornerSize: 8,
3235
3299
  touchCornerSize: 12
@@ -3679,7 +3743,6 @@ class AbstractInteractiveCanvas {
3679
3743
  }
3680
3744
  /**
3681
3745
  * Redraw the canvas.
3682
- * Uses requestAnimationFrame to batch multiple redraw calls into a single render.
3683
3746
  */
3684
3747
  redraw() {
3685
3748
  if (this.redrawScheduled) {
@@ -3815,6 +3878,7 @@ class AbstractInteractiveCanvas {
3815
3878
  * Update the actual dimensions of the canvas by multiplying the requested dimensions by the current zoom factor.
3816
3879
  */
3817
3880
  updateCanvasDimensions(dims, zoom) {
3881
+ const oldWidth = this.fabricCanvas.getWidth();
3818
3882
  const width = dims.widthPx * zoom;
3819
3883
  const height = dims.heightPx * zoom;
3820
3884
  this.fabricCanvas.setZoom(zoom);
@@ -3822,6 +3886,34 @@ class AbstractInteractiveCanvas {
3822
3886
  width,
3823
3887
  height
3824
3888
  });
3889
+ const scaleFactor = width / oldWidth;
3890
+ const objects = this.fabricCanvas.getObjects();
3891
+ objects.forEach((obj) => {
3892
+ obj.set({
3893
+ left: obj.left * scaleFactor,
3894
+ });
3895
+ if (obj.type === "textbox" || obj.type === "text") {
3896
+ obj.set({
3897
+ scaleX: 1,
3898
+ scaleY: 1,
3899
+ width: obj.width * scaleFactor,
3900
+ });
3901
+ }
3902
+ else if (obj.type === "border" || obj.type === "rect") {
3903
+ obj.set({
3904
+ width: obj.width * scaleFactor,
3905
+ scaleX: 1,
3906
+ strokeUniform: true,
3907
+ });
3908
+ }
3909
+ else {
3910
+ obj.set({
3911
+ scaleX: obj.scaleX * scaleFactor,
3912
+ });
3913
+ }
3914
+ obj.setCoords();
3915
+ });
3916
+ this.fabricCanvas.requestRenderAll();
3825
3917
  }
3826
3918
  onProfileChanged(profile) {
3827
3919
  this.setMultiSelect(profile.allowMultiSelect);
@@ -3976,6 +4068,7 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
3976
4068
  this.fabricCanvas.on("selection:created", (event) => this.onFabricSelectionChanged(event));
3977
4069
  this.fabricCanvas.on("selection:cleared", (event) => this.onFabricSelectionChanged(event));
3978
4070
  this.fabricCanvas.on("text:editing:entered", () => this.onTextEditing());
4071
+ //this.fabricCanvas.on("text:editing:exited", () => this.onTextEditing());
3979
4072
  this.fabricCanvas.on("text:editing:exited", (event) => {
3980
4073
  this.onTextEditing();
3981
4074
  const target = event.target;
@@ -3984,6 +4077,8 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
3984
4077
  }
3985
4078
  });
3986
4079
  this.fabricCanvas.on("text:changed", () => this.onTextChange());
4080
+ // Handle double-click on textbox to enter editing mode
4081
+ // @ts-ignore
3987
4082
  this.fabricCanvas.on("mouse:dblclick", (event) => this.onDoubleClick(event));
3988
4083
  }
3989
4084
  /**
@@ -4018,20 +4113,29 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
4018
4113
  selection.setEvent(event);
4019
4114
  }
4020
4115
  this.selection$.next(selection);
4116
+ this.enterTextSelectionModeIfRequired();
4117
+ }
4118
+ enterTextSelectionModeIfRequired() {
4119
+ var _a, _b;
4120
+ if (!this.profile$.value.enterTextEditImmediatelyOnSelect && !this.layoutManager$.value.enterTextEditImmediatelyOnSelect)
4121
+ return;
4122
+ 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)
4123
+ return;
4124
+ const text = this.selection$.value.objects[0];
4125
+ text.enterEditing();
4021
4126
  }
4022
4127
  onTextEditing() {
4023
4128
  const selected = this.getFieldsByCanvasObject(this.fabricCanvas.getActiveObjects());
4024
4129
  this.selection$.next(new SelectionData(selected));
4025
4130
  }
4026
4131
  onDoubleClick(event) {
4027
- var _a, _b;
4028
4132
  const target = event.target;
4029
4133
  if (target && (target.type === 'textbox' || target.type === 'i-text')) {
4030
- const targetIText = target;
4031
- if (!targetIText.isEditing) {
4134
+ if (!target.isEditing) {
4032
4135
  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();
4136
+ // target.enterEditing();
4137
+ // target.hiddenTextarea?.focus();
4138
+ this.enterTextSelectionModeIfRequired();
4035
4139
  }
4036
4140
  }
4037
4141
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leapfrog/interactive-canvas",
3
- "version": "2.0.25-beta-10",
3
+ "version": "2.0.25-beta-12",
4
4
  "description": "Leapfrog interactive canvas",
5
5
  "files": [
6
6
  "dist"
@@ -19,7 +19,7 @@
19
19
  },
20
20
  "dependencies": {
21
21
  "canvas": "^3.2.3",
22
- "fabric": "^7.2.0",
22
+ "fabric": "7.4.0",
23
23
  "lodash": "^4.18.1",
24
24
  "rollup": "^4.60.3",
25
25
  "rollup-plugin-typescript2": "^0.37.0",