@leapfrog/interactive-canvas 2.0.25-beta-14 → 2.0.25-beta-15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/abstract-interactive-canvas.d.ts +2 -0
- package/dist/interactive-canvas.d.ts +0 -1
- package/dist/interactive-canvas.es.js +202 -101
- package/dist/interactive-canvas.js +202 -101
- package/dist/layout/advanced-layout-manager.d.ts +1 -0
- package/dist/layout/standard-layout-manager.d.ts +1 -0
- package/dist/object/abstract-canvas-object.d.ts +7 -0
- package/dist/object/impl/text.d.ts +14 -0
- package/package.json +1 -1
|
@@ -340,8 +340,10 @@ export declare abstract class AbstractInteractiveCanvas implements IInteractiveC
|
|
|
340
340
|
* @inheritDoc
|
|
341
341
|
*/
|
|
342
342
|
cloneObject(sourceObject: FabricObject, objectData: CanvasObjectData): Promise<void>;
|
|
343
|
+
private redrawScheduled;
|
|
343
344
|
/**
|
|
344
345
|
* Redraw the canvas.
|
|
346
|
+
* Uses requestAnimationFrame to batch multiple redraw calls into a single render.
|
|
345
347
|
*/
|
|
346
348
|
redraw(): void;
|
|
347
349
|
syncObjectList(): void;
|
|
@@ -232,9 +232,15 @@ class ResizeCanvasLayoutFunction {
|
|
|
232
232
|
const targetHeight = increase.withSize(increase.width, baseDimensions.height + increase.height);
|
|
233
233
|
if (currentDimensions.height !== targetHeight.height) {
|
|
234
234
|
canvas.setDimensions(targetHeight);
|
|
235
|
+
canvas.getObjectsInternal().forEach((object) => {
|
|
236
|
+
var _a;
|
|
237
|
+
const canvasObj = canvas.getObjectsByTypeInternal(object.type).find(o => o.getId() === object.getId());
|
|
238
|
+
if (canvasObj && (((_a = canvasObj.isPinToBottom) === null || _a === void 0 ? void 0 : _a.call(canvasObj)) || object.top > currentDimensions.heightPx * 0.5)) {
|
|
239
|
+
object.setCoords();
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
canvas.redraw();
|
|
235
243
|
}
|
|
236
|
-
canvas.getObjectsInternal().forEach((object) => object.setCoords());
|
|
237
|
-
canvas.redraw();
|
|
238
244
|
}
|
|
239
245
|
}
|
|
240
246
|
|
|
@@ -264,7 +270,6 @@ class StackObjectsLayoutFunction {
|
|
|
264
270
|
const footerObjects = objects[0];
|
|
265
271
|
const bodyObjects = objects[1];
|
|
266
272
|
const bodyWidth = canvasDimensions.widthPx - canvasMargin.right - canvasMargin.left;
|
|
267
|
-
canvas.redraw();
|
|
268
273
|
//Make one pass and set the widths of all elements. This will force objects with dynamic heights (like text) to take on their necessary height before trying to lay them out.
|
|
269
274
|
this.applyWidths(allObjects, bodyWidth);
|
|
270
275
|
const bodyRows = this.getRows(bodyObjects);
|
|
@@ -351,7 +356,11 @@ class StackObjectsLayoutFunction {
|
|
|
351
356
|
object.setPosition({
|
|
352
357
|
width: actualWidth
|
|
353
358
|
});
|
|
354
|
-
|
|
359
|
+
});
|
|
360
|
+
_(objects).forEach((object) => {
|
|
361
|
+
if (object.getType() !== ObjectType.VERTICAL_RULE) {
|
|
362
|
+
object.updateDimensions();
|
|
363
|
+
}
|
|
355
364
|
});
|
|
356
365
|
}
|
|
357
366
|
adjustCanvasSize(canvas, requiredHeight) {
|
|
@@ -437,15 +446,22 @@ class AdvancedLayoutManager extends AbstractLayoutManager {
|
|
|
437
446
|
this.repositionBackgrounds = new RepositionBackgroundsLayoutFunction();
|
|
438
447
|
this.resizeCanvas = new ResizeCanvasLayoutFunction();
|
|
439
448
|
this.stackObjects = new StackObjectsLayoutFunction();
|
|
449
|
+
this.dimensionsLayoutScheduled = false;
|
|
440
450
|
}
|
|
441
451
|
onCanvasDimensionsChange() {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
this.
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
452
|
+
if (this.dimensionsLayoutScheduled) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
this.dimensionsLayoutScheduled = true;
|
|
456
|
+
Promise.resolve().then(() => {
|
|
457
|
+
this.pinToBottom.execute(this.canvas);
|
|
458
|
+
this.dimensionChange();
|
|
459
|
+
this.repositionCutoffs.execute(this.canvas);
|
|
460
|
+
this.repositionBorders.execute(this.canvas);
|
|
461
|
+
this.repositionBackgrounds.execute(this.canvas);
|
|
462
|
+
this.redrawAll.execute(this.canvas);
|
|
463
|
+
this.dimensionsLayoutScheduled = false;
|
|
464
|
+
});
|
|
449
465
|
}
|
|
450
466
|
onTextChange() {
|
|
451
467
|
this.resizeCanvas.execute(this.canvas);
|
|
@@ -484,6 +500,7 @@ class StandardLayoutManager extends AbstractLayoutManager {
|
|
|
484
500
|
this.repositionBackgrounds = new RepositionBackgroundsLayoutFunction();
|
|
485
501
|
this.stackObjects = new StackObjectsLayoutFunction();
|
|
486
502
|
this.updateObjectDimensions = new UpdateObjectDimensionsLayoutFunction();
|
|
503
|
+
this.layoutBatchScheduled = false;
|
|
487
504
|
}
|
|
488
505
|
/**
|
|
489
506
|
* @override
|
|
@@ -525,12 +542,19 @@ class StandardLayoutManager extends AbstractLayoutManager {
|
|
|
525
542
|
this.stackAndRedraw();
|
|
526
543
|
}
|
|
527
544
|
stackAndRedraw() {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
this.
|
|
532
|
-
|
|
533
|
-
|
|
545
|
+
if (this.layoutBatchScheduled) {
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
this.layoutBatchScheduled = true;
|
|
549
|
+
Promise.resolve().then(() => {
|
|
550
|
+
this.stackObjects.execute(this.canvas);
|
|
551
|
+
this.repositionCutoffs.execute(this.canvas);
|
|
552
|
+
this.repositionBorders.execute(this.canvas);
|
|
553
|
+
this.repositionBackgrounds.execute(this.canvas);
|
|
554
|
+
this.updateObjectDimensions.execute(this.canvas);
|
|
555
|
+
this.redrawAll.execute(this.canvas);
|
|
556
|
+
this.layoutBatchScheduled = false;
|
|
557
|
+
});
|
|
534
558
|
}
|
|
535
559
|
}
|
|
536
560
|
|
|
@@ -997,7 +1021,7 @@ class AbstractCanvasObject {
|
|
|
997
1021
|
//no-op
|
|
998
1022
|
}
|
|
999
1023
|
preventLeavingCanvas() {
|
|
1000
|
-
const overflow = this.
|
|
1024
|
+
const overflow = this.getCanvasOverflowForBoundaryPrevention();
|
|
1001
1025
|
if (overflow.hasOverflow) {
|
|
1002
1026
|
const adjustX = overflow.left || -overflow.right;
|
|
1003
1027
|
const adjustY = overflow.top || -overflow.bottom;
|
|
@@ -1023,6 +1047,29 @@ class AbstractCanvasObject {
|
|
|
1023
1047
|
hasOverflow: top > 0 || right > 0 || bottom > 0 || left > 0
|
|
1024
1048
|
};
|
|
1025
1049
|
}
|
|
1050
|
+
getCanvasOverflowForBoundaryPrevention() {
|
|
1051
|
+
// Use a simpler bounding box calculation for boundary prevention
|
|
1052
|
+
// to avoid issues with decorative elements (like ordered list markers)
|
|
1053
|
+
// extending beyond the main object bounds
|
|
1054
|
+
const bound = this.getSimpleBoundingBox();
|
|
1055
|
+
const canvasDimensions = this.canvas.getDimensions();
|
|
1056
|
+
const top = Math.max(0, 0 - bound.top);
|
|
1057
|
+
const right = Math.max(0, bound.left + bound.width - canvasDimensions.widthPx);
|
|
1058
|
+
const bottom = Math.max(0, bound.top + bound.height - canvasDimensions.heightPx);
|
|
1059
|
+
const left = Math.max(0, 0 - bound.left);
|
|
1060
|
+
return {
|
|
1061
|
+
top: top,
|
|
1062
|
+
right: right,
|
|
1063
|
+
bottom: bottom,
|
|
1064
|
+
left: left,
|
|
1065
|
+
hasOverflow: top > 0 || right > 0 || bottom > 0 || left > 0
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
getSimpleBoundingBox() {
|
|
1069
|
+
// Default implementation: use normal bounding box
|
|
1070
|
+
// Subclasses can override this to provide simpler bounds for drag boundary checks
|
|
1071
|
+
return this.getBoundingBox();
|
|
1072
|
+
}
|
|
1026
1073
|
centerHorizontallyInternal() {
|
|
1027
1074
|
const canvasWidth = this.canvas.getDimensions().widthPx;
|
|
1028
1075
|
const objectWidth = this.getDimensions().width.px;
|
|
@@ -1070,7 +1117,8 @@ class AbstractCanvasObject {
|
|
|
1070
1117
|
mt: options.allowScale,
|
|
1071
1118
|
mr: options.allowScale,
|
|
1072
1119
|
mb: options.allowScale,
|
|
1073
|
-
mtr: options.allowRotate
|
|
1120
|
+
mtr: options.allowRotate,
|
|
1121
|
+
// dragControl: options.allowMove // Show drag control when movement is allowed
|
|
1074
1122
|
});
|
|
1075
1123
|
}
|
|
1076
1124
|
}
|
|
@@ -1794,15 +1842,29 @@ class RichText extends Image {
|
|
|
1794
1842
|
class Text extends AbstractCanvasObject {
|
|
1795
1843
|
constructor(canvas, fabricObject, persistedField) {
|
|
1796
1844
|
super(ObjectType.TEXT, canvas, fabricObject, persistedField);
|
|
1845
|
+
this.notifyCanvasTimeout = null;
|
|
1846
|
+
this.NOTIFY_DELAY_MS = 150; // Debounce canvas notifications
|
|
1797
1847
|
this.canvas = canvas;
|
|
1798
1848
|
this.fontLibrary = canvas.getFontLibrary();
|
|
1799
1849
|
this.baseComponentHeight = this.fabricObject.height;
|
|
1800
1850
|
this.text$ = new BehaviorSubject(this.fabricObject.text);
|
|
1801
1851
|
this.data.userText = this.fabricObject.text;
|
|
1802
1852
|
this.textSelection$ = new BehaviorSubject(new TextSelection(this.fabricObject, this.fontLibrary));
|
|
1803
|
-
this.fabricObject.set({
|
|
1853
|
+
this.fabricObject.set({
|
|
1854
|
+
strokeWidth: 0,
|
|
1855
|
+
perPixelTargetFind: false, // Disable per-pixel detection for this object
|
|
1856
|
+
padding: 0 // Add padding for larger hover area (Fabric v7 compatibility)
|
|
1857
|
+
});
|
|
1804
1858
|
this.fabricObject.on("changed", () => this.onTextChange());
|
|
1805
1859
|
this.fabricObject.on("selection:changed", () => this.onSelectionChange());
|
|
1860
|
+
// Note: Removed containsPoint override to allow Fabric's default bounding box
|
|
1861
|
+
// detection with padding to work properly for hover detection
|
|
1862
|
+
this.fabricObject.on("mousedown", (e) => {
|
|
1863
|
+
var _a;
|
|
1864
|
+
if (!this.fabricObject.isEditing) {
|
|
1865
|
+
(_a = e.e) === null || _a === void 0 ? void 0 : _a.preventDefault();
|
|
1866
|
+
}
|
|
1867
|
+
});
|
|
1806
1868
|
this.applyBulletListMonkeyPatches();
|
|
1807
1869
|
}
|
|
1808
1870
|
/**
|
|
@@ -2154,11 +2216,25 @@ class Text extends AbstractCanvasObject {
|
|
|
2154
2216
|
getBulletMargin() {
|
|
2155
2217
|
if (!this.isBulleted())
|
|
2156
2218
|
return 0;
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2219
|
+
try {
|
|
2220
|
+
let fontSize = 0;
|
|
2221
|
+
each(this.fabricObject._textLines, (textLine, lineIndex) => {
|
|
2222
|
+
const size = this.fabricObject.getValueOfPropertyAt(lineIndex, 0, "fontSize");
|
|
2223
|
+
// Sanity check fontSize
|
|
2224
|
+
if (isFinite(size) && size > 0 && size < 1000) {
|
|
2225
|
+
fontSize = Math.max(fontSize, size);
|
|
2226
|
+
}
|
|
2227
|
+
});
|
|
2228
|
+
// Additional safety check
|
|
2229
|
+
if (!isFinite(fontSize) || fontSize < 0 || fontSize > 1000) {
|
|
2230
|
+
return 0;
|
|
2231
|
+
}
|
|
2232
|
+
return fontSize * 0.6;
|
|
2233
|
+
}
|
|
2234
|
+
catch (e) {
|
|
2235
|
+
console.warn('Error calculating bullet margin:', e);
|
|
2236
|
+
return 0;
|
|
2237
|
+
}
|
|
2162
2238
|
}
|
|
2163
2239
|
/**
|
|
2164
2240
|
* @override
|
|
@@ -2168,10 +2244,35 @@ class Text extends AbstractCanvasObject {
|
|
|
2168
2244
|
if (!this.isOrderedList())
|
|
2169
2245
|
return 0;
|
|
2170
2246
|
const orderedListMarkerEntities = this.fabricObject._orderedListMarkerEntities;
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
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
|
+
}
|
|
2175
2276
|
}
|
|
2176
2277
|
/**
|
|
2177
2278
|
* @override
|
|
@@ -2412,6 +2513,7 @@ class Text extends AbstractCanvasObject {
|
|
|
2412
2513
|
lockMovementY: true,
|
|
2413
2514
|
hasControls: false,
|
|
2414
2515
|
selectable: false,
|
|
2516
|
+
// evented: false, // Don't capture mouse events - allows clicks to pass through to textbox
|
|
2415
2517
|
originX: "left",
|
|
2416
2518
|
originY: "top"
|
|
2417
2519
|
});
|
|
@@ -2506,7 +2608,7 @@ class Text extends AbstractCanvasObject {
|
|
|
2506
2608
|
text: `${lineNumber}.`
|
|
2507
2609
|
});
|
|
2508
2610
|
}
|
|
2509
|
-
if (method === "fillText" && this.fabricObject.left <= 3) {
|
|
2611
|
+
if (method === "fillText" && this.isOrderedList() && this.fabricObject.left <= 3) {
|
|
2510
2612
|
left = left + this.getOrderedListMargin();
|
|
2511
2613
|
}
|
|
2512
2614
|
this.fabricObject.dirty = true;
|
|
@@ -2518,13 +2620,24 @@ class Text extends AbstractCanvasObject {
|
|
|
2518
2620
|
* @inheritDoc
|
|
2519
2621
|
*/
|
|
2520
2622
|
updateFabricObjectBehavior() {
|
|
2623
|
+
// DON'T call super.updateFabricObjectBehavior() for textboxes
|
|
2624
|
+
// because it locks movement which interferes with text editing in Fabric v7
|
|
2521
2625
|
super.updateFabricObjectBehavior();
|
|
2522
2626
|
const profile = this.canvas.getProfile();
|
|
2523
2627
|
const allowManualPositioning = !this.canvas.getLayoutManager().isEnforcedPositioning;
|
|
2628
|
+
// For textboxes, we keep them moveable but hide all controls except drag control
|
|
2629
|
+
// This allows text editing to work properly while still supporting drag via the icon
|
|
2524
2630
|
this.fabricObject.set({
|
|
2631
|
+
lockMovementX: false, // Allow dragging
|
|
2632
|
+
lockMovementY: false, // Allow dragging
|
|
2525
2633
|
lockScalingY: true,
|
|
2526
2634
|
lockScalingX: this.isPinToBottom(),
|
|
2527
|
-
lockScalingFlip: true
|
|
2635
|
+
lockScalingFlip: true,
|
|
2636
|
+
lockRotation: true,
|
|
2637
|
+
selectable: true,
|
|
2638
|
+
editable: false, // Only enable on double-click
|
|
2639
|
+
hoverCursor: "move", // Show move cursor by default
|
|
2640
|
+
moveCursor: "move",
|
|
2528
2641
|
});
|
|
2529
2642
|
this.fabricObject.setControlsVisibility({
|
|
2530
2643
|
tl: false,
|
|
@@ -2534,35 +2647,64 @@ class Text extends AbstractCanvasObject {
|
|
|
2534
2647
|
ml: allowManualPositioning && profile.allowScale && !this.isPinToBottom(),
|
|
2535
2648
|
mt: false,
|
|
2536
2649
|
mr: allowManualPositioning && profile.allowScale && !this.isPinToBottom(),
|
|
2537
|
-
mb: false
|
|
2650
|
+
mb: false,
|
|
2651
|
+
mtr: false,
|
|
2538
2652
|
});
|
|
2539
2653
|
this.redraw();
|
|
2540
2654
|
}
|
|
2541
2655
|
onTextChange() {
|
|
2542
2656
|
this.data.userText = this.fabricObject.text;
|
|
2543
2657
|
this.text$.next(this.fabricObject.text);
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2658
|
+
// Only check width bounds if text is close to canvas edge (performance optimization)
|
|
2659
|
+
const currentLeft = this.fabricObject.left || 0;
|
|
2660
|
+
const canvasWidth = this.canvas.getDimensions().widthPx;
|
|
2661
|
+
// Skip expensive getBoundingBox if object is clearly within bounds
|
|
2662
|
+
if (currentLeft + (this.fabricObject.width || 0) > canvasWidth * 0.9) {
|
|
2663
|
+
if (this.getBoundingBox().width > canvasWidth) {
|
|
2664
|
+
this.fabricObject.set({ left: 0, width: canvasWidth });
|
|
2665
|
+
this.redraw();
|
|
2666
|
+
}
|
|
2547
2667
|
}
|
|
2548
2668
|
}
|
|
2549
2669
|
onDelete() {
|
|
2550
2670
|
this.removeBullets();
|
|
2551
2671
|
}
|
|
2552
2672
|
onSelectionChange() {
|
|
2553
|
-
this.updateTextSelection();
|
|
2673
|
+
this.updateTextSelection(false);
|
|
2554
2674
|
}
|
|
2555
2675
|
hasTextSelection() {
|
|
2556
2676
|
return this.canvas.getSelection().isText && this.textSelection$.value.length > 0;
|
|
2557
2677
|
}
|
|
2558
|
-
updateTextSelection() {
|
|
2678
|
+
updateTextSelection(notifyCanvas = true) {
|
|
2559
2679
|
if (this.canvas.headless)
|
|
2560
2680
|
return;
|
|
2561
2681
|
this.textSelection$.next(new TextSelection(this.fabricObject, this.fontLibrary));
|
|
2562
|
-
|
|
2682
|
+
if (notifyCanvas) {
|
|
2683
|
+
this.notifyCanvasOfTextChanges();
|
|
2684
|
+
}
|
|
2563
2685
|
}
|
|
2564
2686
|
notifyCanvasOfTextChanges() {
|
|
2565
|
-
this.
|
|
2687
|
+
if (this.notifyCanvasTimeout) {
|
|
2688
|
+
clearTimeout(this.notifyCanvasTimeout);
|
|
2689
|
+
}
|
|
2690
|
+
this.notifyCanvasTimeout = setTimeout(() => {
|
|
2691
|
+
this.canvas.notifyTextChanged();
|
|
2692
|
+
this.notifyCanvasTimeout = null;
|
|
2693
|
+
}, this.NOTIFY_DELAY_MS);
|
|
2694
|
+
}
|
|
2695
|
+
/**
|
|
2696
|
+
* @override
|
|
2697
|
+
* Provide a simpler bounding box for drag boundary checks that uses the textbox
|
|
2698
|
+
* dimensions directly instead of getBoundingRect(), which includes ordered list
|
|
2699
|
+
* markers that may extend far beyond the text and cause drag shaking issues.
|
|
2700
|
+
*/
|
|
2701
|
+
getSimpleBoundingBox() {
|
|
2702
|
+
return {
|
|
2703
|
+
top: this.fabricObject.top || 0,
|
|
2704
|
+
left: this.fabricObject.left || 0,
|
|
2705
|
+
width: (this.fabricObject.width || 0) * (this.fabricObject.scaleX || 1),
|
|
2706
|
+
height: (this.fabricObject.height || 0) * (this.fabricObject.scaleY || 1)
|
|
2707
|
+
};
|
|
2566
2708
|
}
|
|
2567
2709
|
}
|
|
2568
2710
|
Text.NEW_LINE = "\n";
|
|
@@ -2728,6 +2870,7 @@ class AbstractInteractiveCanvas {
|
|
|
2728
2870
|
this.devicePixelRatio = devicePixelRatio;
|
|
2729
2871
|
this.fontLibrary = new FontLibrary();
|
|
2730
2872
|
this.dimensionChangeRejections$ = new Subject();
|
|
2873
|
+
this.redrawScheduled = false;
|
|
2731
2874
|
this.IMAGE_CAPTURE_COEFFICIENT = this.calculateImageCaptureCoefficient();
|
|
2732
2875
|
/**
|
|
2733
2876
|
* Fabric defaults to only 2 decimal places when saving, which causes havoc on the scaling of large images.
|
|
@@ -2749,8 +2892,8 @@ class AbstractInteractiveCanvas {
|
|
|
2749
2892
|
//Configure canvas behaviour
|
|
2750
2893
|
this.fabricCanvas.stateful = true;
|
|
2751
2894
|
this.fabricCanvas.preserveObjectStacking = true;
|
|
2752
|
-
this.fabricCanvas.perPixelTargetFind =
|
|
2753
|
-
this.fabricCanvas.targetFindTolerance = 4
|
|
2895
|
+
this.fabricCanvas.perPixelTargetFind = false; // Disabled for better hover tolerance in Fabric v7
|
|
2896
|
+
this.fabricCanvas.targetFindTolerance = 0; // Increased for better hover detection in Fabric v7 (was 4)
|
|
2754
2897
|
this.setBackgroundColor("#FFFFFF");
|
|
2755
2898
|
}
|
|
2756
2899
|
/**
|
|
@@ -3054,7 +3197,6 @@ class AbstractInteractiveCanvas {
|
|
|
3054
3197
|
*/
|
|
3055
3198
|
addImageObject(imageUrl) {
|
|
3056
3199
|
return __awaiter(this, void 0, void 0, function* () {
|
|
3057
|
-
// Don't let the image be more than half the canvas in size.
|
|
3058
3200
|
const maxSizeScale = 2;
|
|
3059
3201
|
const maxWidth = this.dimensions$.value.widthPx / maxSizeScale;
|
|
3060
3202
|
const options = { crossOrigin: "anonymous" };
|
|
@@ -3066,7 +3208,6 @@ class AbstractInteractiveCanvas {
|
|
|
3066
3208
|
if (curWidth > maxWidth) {
|
|
3067
3209
|
newScale = maxWidth / curWidth;
|
|
3068
3210
|
}
|
|
3069
|
-
// Set image properties and add to the canvas
|
|
3070
3211
|
const fabricImage = new this.fabric.Image(image, {
|
|
3071
3212
|
id: id,
|
|
3072
3213
|
scaleX: newScale,
|
|
@@ -3086,7 +3227,6 @@ class AbstractInteractiveCanvas {
|
|
|
3086
3227
|
return object;
|
|
3087
3228
|
}
|
|
3088
3229
|
else {
|
|
3089
|
-
// Browser-side logic
|
|
3090
3230
|
return yield new Promise((resolve) => {
|
|
3091
3231
|
this.fabric.Image.fromURL(imageUrl, options).then((fabricImage) => {
|
|
3092
3232
|
const id = this.randomId();
|
|
@@ -3138,15 +3278,14 @@ class AbstractInteractiveCanvas {
|
|
|
3138
3278
|
fontFamily: profile.defaultFont,
|
|
3139
3279
|
textAlign: profile.defaultTextAlignment,
|
|
3140
3280
|
hoverCursor: "move",
|
|
3141
|
-
moveCursor:
|
|
3281
|
+
moveCursor: "move",
|
|
3282
|
+
editable: false,
|
|
3283
|
+
editingBorderColor: '#2196F3',
|
|
3142
3284
|
multiLine: true,
|
|
3143
3285
|
lineHeight: profile.defaultLineHeight,
|
|
3144
3286
|
charSpacing: profile.defaultCharacterSpacing,
|
|
3145
|
-
perPixelTargetFind: false,
|
|
3146
|
-
targetFindTolerance: 4,
|
|
3147
3287
|
splitByGrapheme: true,
|
|
3148
|
-
|
|
3149
|
-
padding: 0,
|
|
3288
|
+
padding: 0, // Increased from 0 for better hover detection in Fabric v7
|
|
3150
3289
|
cornerSize: 8,
|
|
3151
3290
|
touchCornerSize: 12
|
|
3152
3291
|
};
|
|
@@ -3595,9 +3734,17 @@ class AbstractInteractiveCanvas {
|
|
|
3595
3734
|
}
|
|
3596
3735
|
/**
|
|
3597
3736
|
* Redraw the canvas.
|
|
3737
|
+
* Uses requestAnimationFrame to batch multiple redraw calls into a single render.
|
|
3598
3738
|
*/
|
|
3599
3739
|
redraw() {
|
|
3600
|
-
this.
|
|
3740
|
+
if (this.redrawScheduled) {
|
|
3741
|
+
return;
|
|
3742
|
+
}
|
|
3743
|
+
this.redrawScheduled = true;
|
|
3744
|
+
requestAnimationFrame(() => {
|
|
3745
|
+
this.fabricCanvas.requestRenderAll();
|
|
3746
|
+
this.redrawScheduled = false;
|
|
3747
|
+
});
|
|
3601
3748
|
}
|
|
3602
3749
|
syncObjectList() {
|
|
3603
3750
|
const newObjectList = _(this.fabricCanvas.getObjects())
|
|
@@ -3723,7 +3870,6 @@ class AbstractInteractiveCanvas {
|
|
|
3723
3870
|
* Update the actual dimensions of the canvas by multiplying the requested dimensions by the current zoom factor.
|
|
3724
3871
|
*/
|
|
3725
3872
|
updateCanvasDimensions(dims, zoom) {
|
|
3726
|
-
//const oldWidth = this.fabricCanvas.getWidth();
|
|
3727
3873
|
const width = dims.widthPx * zoom;
|
|
3728
3874
|
const height = dims.heightPx * zoom;
|
|
3729
3875
|
this.fabricCanvas.setZoom(zoom);
|
|
@@ -3731,39 +3877,6 @@ class AbstractInteractiveCanvas {
|
|
|
3731
3877
|
width,
|
|
3732
3878
|
height
|
|
3733
3879
|
});
|
|
3734
|
-
/*if (this.layoutManager$.value instanceof AdvancedLayoutManager) {
|
|
3735
|
-
const scaleFactor = width / oldWidth;
|
|
3736
|
-
//this.fabricCanvas.calcOffset();
|
|
3737
|
-
const objects = this.fabricCanvas.getObjects();
|
|
3738
|
-
objects.forEach((obj: any) => {
|
|
3739
|
-
obj.set({ ₹
|
|
3740
|
-
left: obj.left * scaleFactor,
|
|
3741
|
-
});
|
|
3742
|
-
if (obj.type === "textbox" || obj.type === "text") {
|
|
3743
|
-
obj.set({
|
|
3744
|
-
scaleX: 1,
|
|
3745
|
-
scaleY: 1,
|
|
3746
|
-
// scaleX: obj.scaleX * scaleFactor,
|
|
3747
|
-
// scaleY: obj.scaleY * scaleFactor,
|
|
3748
|
-
width: obj.width * scaleFactor,
|
|
3749
|
-
});
|
|
3750
|
-
// obj.initDimensions();
|
|
3751
|
-
} else if (obj.type === "border" || obj.type === "rect") {
|
|
3752
|
-
obj.set({
|
|
3753
|
-
width: obj.width * scaleFactor,
|
|
3754
|
-
scaleX: 1,
|
|
3755
|
-
strokeUniform: true,
|
|
3756
|
-
// strokeAlign: "inner",
|
|
3757
|
-
});
|
|
3758
|
-
} else {
|
|
3759
|
-
obj.set({
|
|
3760
|
-
scaleX: obj.scaleX * scaleFactor,
|
|
3761
|
-
});
|
|
3762
|
-
}
|
|
3763
|
-
obj.setCoords();
|
|
3764
|
-
});
|
|
3765
|
-
this.fabricCanvas.requestRenderAll();
|
|
3766
|
-
}*/
|
|
3767
3880
|
}
|
|
3768
3881
|
onProfileChanged(profile) {
|
|
3769
3882
|
this.setMultiSelect(profile.allowMultiSelect);
|
|
@@ -3918,7 +4031,6 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
|
|
|
3918
4031
|
this.fabricCanvas.on("selection:created", (event) => this.onFabricSelectionChanged(event));
|
|
3919
4032
|
this.fabricCanvas.on("selection:cleared", (event) => this.onFabricSelectionChanged(event));
|
|
3920
4033
|
this.fabricCanvas.on("text:editing:entered", () => this.onTextEditing());
|
|
3921
|
-
this.fabricCanvas.on("text:editing:exited", () => this.onTextEditing());
|
|
3922
4034
|
this.fabricCanvas.on("text:editing:exited", (event) => {
|
|
3923
4035
|
this.onTextEditing();
|
|
3924
4036
|
const target = event.target;
|
|
@@ -3927,8 +4039,6 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
|
|
|
3927
4039
|
}
|
|
3928
4040
|
});
|
|
3929
4041
|
this.fabricCanvas.on("text:changed", () => this.onTextChange());
|
|
3930
|
-
// Handle double-click on textbox to enter editing mode
|
|
3931
|
-
// @ts-ignore
|
|
3932
4042
|
this.fabricCanvas.on("mouse:dblclick", (event) => this.onDoubleClick(event));
|
|
3933
4043
|
}
|
|
3934
4044
|
/**
|
|
@@ -3963,29 +4073,20 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
|
|
|
3963
4073
|
selection.setEvent(event);
|
|
3964
4074
|
}
|
|
3965
4075
|
this.selection$.next(selection);
|
|
3966
|
-
this.enterTextSelectionModeIfRequired();
|
|
3967
|
-
}
|
|
3968
|
-
enterTextSelectionModeIfRequired() {
|
|
3969
|
-
var _a, _b;
|
|
3970
|
-
if (!this.profile$.value.enterTextEditImmediatelyOnSelect && !this.layoutManager$.value.enterTextEditImmediatelyOnSelect)
|
|
3971
|
-
return;
|
|
3972
|
-
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)
|
|
3973
|
-
return;
|
|
3974
|
-
const text = this.selection$.value.objects[0];
|
|
3975
|
-
text.enterEditing();
|
|
3976
4076
|
}
|
|
3977
4077
|
onTextEditing() {
|
|
3978
4078
|
const selected = this.getFieldsByCanvasObject(this.fabricCanvas.getActiveObjects());
|
|
3979
4079
|
this.selection$.next(new SelectionData(selected));
|
|
3980
4080
|
}
|
|
3981
4081
|
onDoubleClick(event) {
|
|
4082
|
+
var _a, _b;
|
|
3982
4083
|
const target = event.target;
|
|
3983
4084
|
if (target && (target.type === 'textbox' || target.type === 'i-text')) {
|
|
3984
|
-
|
|
4085
|
+
const targetIText = target;
|
|
4086
|
+
if (!targetIText.isEditing) {
|
|
3985
4087
|
target.set({ editable: true, hoverCursor: 'text' });
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
this.enterTextSelectionModeIfRequired();
|
|
4088
|
+
(_a = targetIText.enterEditing) === null || _a === void 0 ? void 0 : _a.call(targetIText);
|
|
4089
|
+
(_b = targetIText.hiddenTextarea) === null || _b === void 0 ? void 0 : _b.focus();
|
|
3989
4090
|
}
|
|
3990
4091
|
}
|
|
3991
4092
|
}
|
|
@@ -253,9 +253,15 @@ 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();
|
|
256
264
|
}
|
|
257
|
-
canvas.getObjectsInternal().forEach((object) => object.setCoords());
|
|
258
|
-
canvas.redraw();
|
|
259
265
|
}
|
|
260
266
|
}
|
|
261
267
|
|
|
@@ -285,7 +291,6 @@ class StackObjectsLayoutFunction {
|
|
|
285
291
|
const footerObjects = objects[0];
|
|
286
292
|
const bodyObjects = objects[1];
|
|
287
293
|
const bodyWidth = canvasDimensions.widthPx - canvasMargin.right - canvasMargin.left;
|
|
288
|
-
canvas.redraw();
|
|
289
294
|
//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.
|
|
290
295
|
this.applyWidths(allObjects, bodyWidth);
|
|
291
296
|
const bodyRows = this.getRows(bodyObjects);
|
|
@@ -372,7 +377,11 @@ class StackObjectsLayoutFunction {
|
|
|
372
377
|
object.setPosition({
|
|
373
378
|
width: actualWidth
|
|
374
379
|
});
|
|
375
|
-
|
|
380
|
+
});
|
|
381
|
+
_(objects).forEach((object) => {
|
|
382
|
+
if (object.getType() !== exports.ObjectType.VERTICAL_RULE) {
|
|
383
|
+
object.updateDimensions();
|
|
384
|
+
}
|
|
376
385
|
});
|
|
377
386
|
}
|
|
378
387
|
adjustCanvasSize(canvas, requiredHeight) {
|
|
@@ -458,15 +467,22 @@ class AdvancedLayoutManager extends AbstractLayoutManager {
|
|
|
458
467
|
this.repositionBackgrounds = new RepositionBackgroundsLayoutFunction();
|
|
459
468
|
this.resizeCanvas = new ResizeCanvasLayoutFunction();
|
|
460
469
|
this.stackObjects = new StackObjectsLayoutFunction();
|
|
470
|
+
this.dimensionsLayoutScheduled = false;
|
|
461
471
|
}
|
|
462
472
|
onCanvasDimensionsChange() {
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
this.
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
473
|
+
if (this.dimensionsLayoutScheduled) {
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
this.dimensionsLayoutScheduled = true;
|
|
477
|
+
Promise.resolve().then(() => {
|
|
478
|
+
this.pinToBottom.execute(this.canvas);
|
|
479
|
+
this.dimensionChange();
|
|
480
|
+
this.repositionCutoffs.execute(this.canvas);
|
|
481
|
+
this.repositionBorders.execute(this.canvas);
|
|
482
|
+
this.repositionBackgrounds.execute(this.canvas);
|
|
483
|
+
this.redrawAll.execute(this.canvas);
|
|
484
|
+
this.dimensionsLayoutScheduled = false;
|
|
485
|
+
});
|
|
470
486
|
}
|
|
471
487
|
onTextChange() {
|
|
472
488
|
this.resizeCanvas.execute(this.canvas);
|
|
@@ -505,6 +521,7 @@ class StandardLayoutManager extends AbstractLayoutManager {
|
|
|
505
521
|
this.repositionBackgrounds = new RepositionBackgroundsLayoutFunction();
|
|
506
522
|
this.stackObjects = new StackObjectsLayoutFunction();
|
|
507
523
|
this.updateObjectDimensions = new UpdateObjectDimensionsLayoutFunction();
|
|
524
|
+
this.layoutBatchScheduled = false;
|
|
508
525
|
}
|
|
509
526
|
/**
|
|
510
527
|
* @override
|
|
@@ -546,12 +563,19 @@ class StandardLayoutManager extends AbstractLayoutManager {
|
|
|
546
563
|
this.stackAndRedraw();
|
|
547
564
|
}
|
|
548
565
|
stackAndRedraw() {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
this.
|
|
553
|
-
|
|
554
|
-
|
|
566
|
+
if (this.layoutBatchScheduled) {
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
this.layoutBatchScheduled = true;
|
|
570
|
+
Promise.resolve().then(() => {
|
|
571
|
+
this.stackObjects.execute(this.canvas);
|
|
572
|
+
this.repositionCutoffs.execute(this.canvas);
|
|
573
|
+
this.repositionBorders.execute(this.canvas);
|
|
574
|
+
this.repositionBackgrounds.execute(this.canvas);
|
|
575
|
+
this.updateObjectDimensions.execute(this.canvas);
|
|
576
|
+
this.redrawAll.execute(this.canvas);
|
|
577
|
+
this.layoutBatchScheduled = false;
|
|
578
|
+
});
|
|
555
579
|
}
|
|
556
580
|
}
|
|
557
581
|
|
|
@@ -1018,7 +1042,7 @@ class AbstractCanvasObject {
|
|
|
1018
1042
|
//no-op
|
|
1019
1043
|
}
|
|
1020
1044
|
preventLeavingCanvas() {
|
|
1021
|
-
const overflow = this.
|
|
1045
|
+
const overflow = this.getCanvasOverflowForBoundaryPrevention();
|
|
1022
1046
|
if (overflow.hasOverflow) {
|
|
1023
1047
|
const adjustX = overflow.left || -overflow.right;
|
|
1024
1048
|
const adjustY = overflow.top || -overflow.bottom;
|
|
@@ -1044,6 +1068,29 @@ class AbstractCanvasObject {
|
|
|
1044
1068
|
hasOverflow: top > 0 || right > 0 || bottom > 0 || left > 0
|
|
1045
1069
|
};
|
|
1046
1070
|
}
|
|
1071
|
+
getCanvasOverflowForBoundaryPrevention() {
|
|
1072
|
+
// Use a simpler bounding box calculation for boundary prevention
|
|
1073
|
+
// to avoid issues with decorative elements (like ordered list markers)
|
|
1074
|
+
// extending beyond the main object bounds
|
|
1075
|
+
const bound = this.getSimpleBoundingBox();
|
|
1076
|
+
const canvasDimensions = this.canvas.getDimensions();
|
|
1077
|
+
const top = Math.max(0, 0 - bound.top);
|
|
1078
|
+
const right = Math.max(0, bound.left + bound.width - canvasDimensions.widthPx);
|
|
1079
|
+
const bottom = Math.max(0, bound.top + bound.height - canvasDimensions.heightPx);
|
|
1080
|
+
const left = Math.max(0, 0 - bound.left);
|
|
1081
|
+
return {
|
|
1082
|
+
top: top,
|
|
1083
|
+
right: right,
|
|
1084
|
+
bottom: bottom,
|
|
1085
|
+
left: left,
|
|
1086
|
+
hasOverflow: top > 0 || right > 0 || bottom > 0 || left > 0
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
getSimpleBoundingBox() {
|
|
1090
|
+
// Default implementation: use normal bounding box
|
|
1091
|
+
// Subclasses can override this to provide simpler bounds for drag boundary checks
|
|
1092
|
+
return this.getBoundingBox();
|
|
1093
|
+
}
|
|
1047
1094
|
centerHorizontallyInternal() {
|
|
1048
1095
|
const canvasWidth = this.canvas.getDimensions().widthPx;
|
|
1049
1096
|
const objectWidth = this.getDimensions().width.px;
|
|
@@ -1091,7 +1138,8 @@ class AbstractCanvasObject {
|
|
|
1091
1138
|
mt: options.allowScale,
|
|
1092
1139
|
mr: options.allowScale,
|
|
1093
1140
|
mb: options.allowScale,
|
|
1094
|
-
mtr: options.allowRotate
|
|
1141
|
+
mtr: options.allowRotate,
|
|
1142
|
+
// dragControl: options.allowMove // Show drag control when movement is allowed
|
|
1095
1143
|
});
|
|
1096
1144
|
}
|
|
1097
1145
|
}
|
|
@@ -1815,15 +1863,29 @@ class RichText extends Image {
|
|
|
1815
1863
|
class Text extends AbstractCanvasObject {
|
|
1816
1864
|
constructor(canvas, fabricObject, persistedField) {
|
|
1817
1865
|
super(exports.ObjectType.TEXT, canvas, fabricObject, persistedField);
|
|
1866
|
+
this.notifyCanvasTimeout = null;
|
|
1867
|
+
this.NOTIFY_DELAY_MS = 150; // Debounce canvas notifications
|
|
1818
1868
|
this.canvas = canvas;
|
|
1819
1869
|
this.fontLibrary = canvas.getFontLibrary();
|
|
1820
1870
|
this.baseComponentHeight = this.fabricObject.height;
|
|
1821
1871
|
this.text$ = new rxjs.BehaviorSubject(this.fabricObject.text);
|
|
1822
1872
|
this.data.userText = this.fabricObject.text;
|
|
1823
1873
|
this.textSelection$ = new rxjs.BehaviorSubject(new TextSelection(this.fabricObject, this.fontLibrary));
|
|
1824
|
-
this.fabricObject.set({
|
|
1874
|
+
this.fabricObject.set({
|
|
1875
|
+
strokeWidth: 0,
|
|
1876
|
+
perPixelTargetFind: false, // Disable per-pixel detection for this object
|
|
1877
|
+
padding: 0 // Add padding for larger hover area (Fabric v7 compatibility)
|
|
1878
|
+
});
|
|
1825
1879
|
this.fabricObject.on("changed", () => this.onTextChange());
|
|
1826
1880
|
this.fabricObject.on("selection:changed", () => this.onSelectionChange());
|
|
1881
|
+
// Note: Removed containsPoint override to allow Fabric's default bounding box
|
|
1882
|
+
// detection with padding to work properly for hover detection
|
|
1883
|
+
this.fabricObject.on("mousedown", (e) => {
|
|
1884
|
+
var _a;
|
|
1885
|
+
if (!this.fabricObject.isEditing) {
|
|
1886
|
+
(_a = e.e) === null || _a === void 0 ? void 0 : _a.preventDefault();
|
|
1887
|
+
}
|
|
1888
|
+
});
|
|
1827
1889
|
this.applyBulletListMonkeyPatches();
|
|
1828
1890
|
}
|
|
1829
1891
|
/**
|
|
@@ -2175,11 +2237,25 @@ class Text extends AbstractCanvasObject {
|
|
|
2175
2237
|
getBulletMargin() {
|
|
2176
2238
|
if (!this.isBulleted())
|
|
2177
2239
|
return 0;
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2240
|
+
try {
|
|
2241
|
+
let fontSize = 0;
|
|
2242
|
+
_.each(this.fabricObject._textLines, (textLine, lineIndex) => {
|
|
2243
|
+
const size = this.fabricObject.getValueOfPropertyAt(lineIndex, 0, "fontSize");
|
|
2244
|
+
// Sanity check fontSize
|
|
2245
|
+
if (isFinite(size) && size > 0 && size < 1000) {
|
|
2246
|
+
fontSize = Math.max(fontSize, size);
|
|
2247
|
+
}
|
|
2248
|
+
});
|
|
2249
|
+
// Additional safety check
|
|
2250
|
+
if (!isFinite(fontSize) || fontSize < 0 || fontSize > 1000) {
|
|
2251
|
+
return 0;
|
|
2252
|
+
}
|
|
2253
|
+
return fontSize * 0.6;
|
|
2254
|
+
}
|
|
2255
|
+
catch (e) {
|
|
2256
|
+
console.warn('Error calculating bullet margin:', e);
|
|
2257
|
+
return 0;
|
|
2258
|
+
}
|
|
2183
2259
|
}
|
|
2184
2260
|
/**
|
|
2185
2261
|
* @override
|
|
@@ -2189,10 +2265,35 @@ class Text extends AbstractCanvasObject {
|
|
|
2189
2265
|
if (!this.isOrderedList())
|
|
2190
2266
|
return 0;
|
|
2191
2267
|
const orderedListMarkerEntities = this.fabricObject._orderedListMarkerEntities;
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
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
|
+
}
|
|
2196
2297
|
}
|
|
2197
2298
|
/**
|
|
2198
2299
|
* @override
|
|
@@ -2433,6 +2534,7 @@ class Text extends AbstractCanvasObject {
|
|
|
2433
2534
|
lockMovementY: true,
|
|
2434
2535
|
hasControls: false,
|
|
2435
2536
|
selectable: false,
|
|
2537
|
+
// evented: false, // Don't capture mouse events - allows clicks to pass through to textbox
|
|
2436
2538
|
originX: "left",
|
|
2437
2539
|
originY: "top"
|
|
2438
2540
|
});
|
|
@@ -2527,7 +2629,7 @@ class Text extends AbstractCanvasObject {
|
|
|
2527
2629
|
text: `${lineNumber}.`
|
|
2528
2630
|
});
|
|
2529
2631
|
}
|
|
2530
|
-
if (method === "fillText" && this.fabricObject.left <= 3) {
|
|
2632
|
+
if (method === "fillText" && this.isOrderedList() && this.fabricObject.left <= 3) {
|
|
2531
2633
|
left = left + this.getOrderedListMargin();
|
|
2532
2634
|
}
|
|
2533
2635
|
this.fabricObject.dirty = true;
|
|
@@ -2539,13 +2641,24 @@ class Text extends AbstractCanvasObject {
|
|
|
2539
2641
|
* @inheritDoc
|
|
2540
2642
|
*/
|
|
2541
2643
|
updateFabricObjectBehavior() {
|
|
2644
|
+
// DON'T call super.updateFabricObjectBehavior() for textboxes
|
|
2645
|
+
// because it locks movement which interferes with text editing in Fabric v7
|
|
2542
2646
|
super.updateFabricObjectBehavior();
|
|
2543
2647
|
const profile = this.canvas.getProfile();
|
|
2544
2648
|
const allowManualPositioning = !this.canvas.getLayoutManager().isEnforcedPositioning;
|
|
2649
|
+
// For textboxes, we keep them moveable but hide all controls except drag control
|
|
2650
|
+
// This allows text editing to work properly while still supporting drag via the icon
|
|
2545
2651
|
this.fabricObject.set({
|
|
2652
|
+
lockMovementX: false, // Allow dragging
|
|
2653
|
+
lockMovementY: false, // Allow dragging
|
|
2546
2654
|
lockScalingY: true,
|
|
2547
2655
|
lockScalingX: this.isPinToBottom(),
|
|
2548
|
-
lockScalingFlip: true
|
|
2656
|
+
lockScalingFlip: true,
|
|
2657
|
+
lockRotation: true,
|
|
2658
|
+
selectable: true,
|
|
2659
|
+
editable: false, // Only enable on double-click
|
|
2660
|
+
hoverCursor: "move", // Show move cursor by default
|
|
2661
|
+
moveCursor: "move",
|
|
2549
2662
|
});
|
|
2550
2663
|
this.fabricObject.setControlsVisibility({
|
|
2551
2664
|
tl: false,
|
|
@@ -2555,35 +2668,64 @@ class Text extends AbstractCanvasObject {
|
|
|
2555
2668
|
ml: allowManualPositioning && profile.allowScale && !this.isPinToBottom(),
|
|
2556
2669
|
mt: false,
|
|
2557
2670
|
mr: allowManualPositioning && profile.allowScale && !this.isPinToBottom(),
|
|
2558
|
-
mb: false
|
|
2671
|
+
mb: false,
|
|
2672
|
+
mtr: false,
|
|
2559
2673
|
});
|
|
2560
2674
|
this.redraw();
|
|
2561
2675
|
}
|
|
2562
2676
|
onTextChange() {
|
|
2563
2677
|
this.data.userText = this.fabricObject.text;
|
|
2564
2678
|
this.text$.next(this.fabricObject.text);
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2679
|
+
// Only check width bounds if text is close to canvas edge (performance optimization)
|
|
2680
|
+
const currentLeft = this.fabricObject.left || 0;
|
|
2681
|
+
const canvasWidth = this.canvas.getDimensions().widthPx;
|
|
2682
|
+
// Skip expensive getBoundingBox if object is clearly within bounds
|
|
2683
|
+
if (currentLeft + (this.fabricObject.width || 0) > canvasWidth * 0.9) {
|
|
2684
|
+
if (this.getBoundingBox().width > canvasWidth) {
|
|
2685
|
+
this.fabricObject.set({ left: 0, width: canvasWidth });
|
|
2686
|
+
this.redraw();
|
|
2687
|
+
}
|
|
2568
2688
|
}
|
|
2569
2689
|
}
|
|
2570
2690
|
onDelete() {
|
|
2571
2691
|
this.removeBullets();
|
|
2572
2692
|
}
|
|
2573
2693
|
onSelectionChange() {
|
|
2574
|
-
this.updateTextSelection();
|
|
2694
|
+
this.updateTextSelection(false);
|
|
2575
2695
|
}
|
|
2576
2696
|
hasTextSelection() {
|
|
2577
2697
|
return this.canvas.getSelection().isText && this.textSelection$.value.length > 0;
|
|
2578
2698
|
}
|
|
2579
|
-
updateTextSelection() {
|
|
2699
|
+
updateTextSelection(notifyCanvas = true) {
|
|
2580
2700
|
if (this.canvas.headless)
|
|
2581
2701
|
return;
|
|
2582
2702
|
this.textSelection$.next(new TextSelection(this.fabricObject, this.fontLibrary));
|
|
2583
|
-
|
|
2703
|
+
if (notifyCanvas) {
|
|
2704
|
+
this.notifyCanvasOfTextChanges();
|
|
2705
|
+
}
|
|
2584
2706
|
}
|
|
2585
2707
|
notifyCanvasOfTextChanges() {
|
|
2586
|
-
this.
|
|
2708
|
+
if (this.notifyCanvasTimeout) {
|
|
2709
|
+
clearTimeout(this.notifyCanvasTimeout);
|
|
2710
|
+
}
|
|
2711
|
+
this.notifyCanvasTimeout = setTimeout(() => {
|
|
2712
|
+
this.canvas.notifyTextChanged();
|
|
2713
|
+
this.notifyCanvasTimeout = null;
|
|
2714
|
+
}, this.NOTIFY_DELAY_MS);
|
|
2715
|
+
}
|
|
2716
|
+
/**
|
|
2717
|
+
* @override
|
|
2718
|
+
* Provide a simpler bounding box for drag boundary checks that uses the textbox
|
|
2719
|
+
* dimensions directly instead of getBoundingRect(), which includes ordered list
|
|
2720
|
+
* markers that may extend far beyond the text and cause drag shaking issues.
|
|
2721
|
+
*/
|
|
2722
|
+
getSimpleBoundingBox() {
|
|
2723
|
+
return {
|
|
2724
|
+
top: this.fabricObject.top || 0,
|
|
2725
|
+
left: this.fabricObject.left || 0,
|
|
2726
|
+
width: (this.fabricObject.width || 0) * (this.fabricObject.scaleX || 1),
|
|
2727
|
+
height: (this.fabricObject.height || 0) * (this.fabricObject.scaleY || 1)
|
|
2728
|
+
};
|
|
2587
2729
|
}
|
|
2588
2730
|
}
|
|
2589
2731
|
Text.NEW_LINE = "\n";
|
|
@@ -2749,6 +2891,7 @@ class AbstractInteractiveCanvas {
|
|
|
2749
2891
|
this.devicePixelRatio = devicePixelRatio;
|
|
2750
2892
|
this.fontLibrary = new FontLibrary();
|
|
2751
2893
|
this.dimensionChangeRejections$ = new rxjs.Subject();
|
|
2894
|
+
this.redrawScheduled = false;
|
|
2752
2895
|
this.IMAGE_CAPTURE_COEFFICIENT = this.calculateImageCaptureCoefficient();
|
|
2753
2896
|
/**
|
|
2754
2897
|
* Fabric defaults to only 2 decimal places when saving, which causes havoc on the scaling of large images.
|
|
@@ -2770,8 +2913,8 @@ class AbstractInteractiveCanvas {
|
|
|
2770
2913
|
//Configure canvas behaviour
|
|
2771
2914
|
this.fabricCanvas.stateful = true;
|
|
2772
2915
|
this.fabricCanvas.preserveObjectStacking = true;
|
|
2773
|
-
this.fabricCanvas.perPixelTargetFind =
|
|
2774
|
-
this.fabricCanvas.targetFindTolerance = 4
|
|
2916
|
+
this.fabricCanvas.perPixelTargetFind = false; // Disabled for better hover tolerance in Fabric v7
|
|
2917
|
+
this.fabricCanvas.targetFindTolerance = 0; // Increased for better hover detection in Fabric v7 (was 4)
|
|
2775
2918
|
this.setBackgroundColor("#FFFFFF");
|
|
2776
2919
|
}
|
|
2777
2920
|
/**
|
|
@@ -3075,7 +3218,6 @@ class AbstractInteractiveCanvas {
|
|
|
3075
3218
|
*/
|
|
3076
3219
|
addImageObject(imageUrl) {
|
|
3077
3220
|
return __awaiter(this, void 0, void 0, function* () {
|
|
3078
|
-
// Don't let the image be more than half the canvas in size.
|
|
3079
3221
|
const maxSizeScale = 2;
|
|
3080
3222
|
const maxWidth = this.dimensions$.value.widthPx / maxSizeScale;
|
|
3081
3223
|
const options = { crossOrigin: "anonymous" };
|
|
@@ -3087,7 +3229,6 @@ class AbstractInteractiveCanvas {
|
|
|
3087
3229
|
if (curWidth > maxWidth) {
|
|
3088
3230
|
newScale = maxWidth / curWidth;
|
|
3089
3231
|
}
|
|
3090
|
-
// Set image properties and add to the canvas
|
|
3091
3232
|
const fabricImage = new this.fabric.Image(image, {
|
|
3092
3233
|
id: id,
|
|
3093
3234
|
scaleX: newScale,
|
|
@@ -3107,7 +3248,6 @@ class AbstractInteractiveCanvas {
|
|
|
3107
3248
|
return object;
|
|
3108
3249
|
}
|
|
3109
3250
|
else {
|
|
3110
|
-
// Browser-side logic
|
|
3111
3251
|
return yield new Promise((resolve) => {
|
|
3112
3252
|
this.fabric.Image.fromURL(imageUrl, options).then((fabricImage) => {
|
|
3113
3253
|
const id = this.randomId();
|
|
@@ -3159,15 +3299,14 @@ class AbstractInteractiveCanvas {
|
|
|
3159
3299
|
fontFamily: profile.defaultFont,
|
|
3160
3300
|
textAlign: profile.defaultTextAlignment,
|
|
3161
3301
|
hoverCursor: "move",
|
|
3162
|
-
moveCursor:
|
|
3302
|
+
moveCursor: "move",
|
|
3303
|
+
editable: false,
|
|
3304
|
+
editingBorderColor: '#2196F3',
|
|
3163
3305
|
multiLine: true,
|
|
3164
3306
|
lineHeight: profile.defaultLineHeight,
|
|
3165
3307
|
charSpacing: profile.defaultCharacterSpacing,
|
|
3166
|
-
perPixelTargetFind: false,
|
|
3167
|
-
targetFindTolerance: 4,
|
|
3168
3308
|
splitByGrapheme: true,
|
|
3169
|
-
|
|
3170
|
-
padding: 0,
|
|
3309
|
+
padding: 0, // Increased from 0 for better hover detection in Fabric v7
|
|
3171
3310
|
cornerSize: 8,
|
|
3172
3311
|
touchCornerSize: 12
|
|
3173
3312
|
};
|
|
@@ -3616,9 +3755,17 @@ class AbstractInteractiveCanvas {
|
|
|
3616
3755
|
}
|
|
3617
3756
|
/**
|
|
3618
3757
|
* Redraw the canvas.
|
|
3758
|
+
* Uses requestAnimationFrame to batch multiple redraw calls into a single render.
|
|
3619
3759
|
*/
|
|
3620
3760
|
redraw() {
|
|
3621
|
-
this.
|
|
3761
|
+
if (this.redrawScheduled) {
|
|
3762
|
+
return;
|
|
3763
|
+
}
|
|
3764
|
+
this.redrawScheduled = true;
|
|
3765
|
+
requestAnimationFrame(() => {
|
|
3766
|
+
this.fabricCanvas.requestRenderAll();
|
|
3767
|
+
this.redrawScheduled = false;
|
|
3768
|
+
});
|
|
3622
3769
|
}
|
|
3623
3770
|
syncObjectList() {
|
|
3624
3771
|
const newObjectList = _(this.fabricCanvas.getObjects())
|
|
@@ -3744,7 +3891,6 @@ class AbstractInteractiveCanvas {
|
|
|
3744
3891
|
* Update the actual dimensions of the canvas by multiplying the requested dimensions by the current zoom factor.
|
|
3745
3892
|
*/
|
|
3746
3893
|
updateCanvasDimensions(dims, zoom) {
|
|
3747
|
-
//const oldWidth = this.fabricCanvas.getWidth();
|
|
3748
3894
|
const width = dims.widthPx * zoom;
|
|
3749
3895
|
const height = dims.heightPx * zoom;
|
|
3750
3896
|
this.fabricCanvas.setZoom(zoom);
|
|
@@ -3752,39 +3898,6 @@ class AbstractInteractiveCanvas {
|
|
|
3752
3898
|
width,
|
|
3753
3899
|
height
|
|
3754
3900
|
});
|
|
3755
|
-
/*if (this.layoutManager$.value instanceof AdvancedLayoutManager) {
|
|
3756
|
-
const scaleFactor = width / oldWidth;
|
|
3757
|
-
//this.fabricCanvas.calcOffset();
|
|
3758
|
-
const objects = this.fabricCanvas.getObjects();
|
|
3759
|
-
objects.forEach((obj: any) => {
|
|
3760
|
-
obj.set({ ₹
|
|
3761
|
-
left: obj.left * scaleFactor,
|
|
3762
|
-
});
|
|
3763
|
-
if (obj.type === "textbox" || obj.type === "text") {
|
|
3764
|
-
obj.set({
|
|
3765
|
-
scaleX: 1,
|
|
3766
|
-
scaleY: 1,
|
|
3767
|
-
// scaleX: obj.scaleX * scaleFactor,
|
|
3768
|
-
// scaleY: obj.scaleY * scaleFactor,
|
|
3769
|
-
width: obj.width * scaleFactor,
|
|
3770
|
-
});
|
|
3771
|
-
// obj.initDimensions();
|
|
3772
|
-
} else if (obj.type === "border" || obj.type === "rect") {
|
|
3773
|
-
obj.set({
|
|
3774
|
-
width: obj.width * scaleFactor,
|
|
3775
|
-
scaleX: 1,
|
|
3776
|
-
strokeUniform: true,
|
|
3777
|
-
// strokeAlign: "inner",
|
|
3778
|
-
});
|
|
3779
|
-
} else {
|
|
3780
|
-
obj.set({
|
|
3781
|
-
scaleX: obj.scaleX * scaleFactor,
|
|
3782
|
-
});
|
|
3783
|
-
}
|
|
3784
|
-
obj.setCoords();
|
|
3785
|
-
});
|
|
3786
|
-
this.fabricCanvas.requestRenderAll();
|
|
3787
|
-
}*/
|
|
3788
3901
|
}
|
|
3789
3902
|
onProfileChanged(profile) {
|
|
3790
3903
|
this.setMultiSelect(profile.allowMultiSelect);
|
|
@@ -3939,7 +4052,6 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
|
|
|
3939
4052
|
this.fabricCanvas.on("selection:created", (event) => this.onFabricSelectionChanged(event));
|
|
3940
4053
|
this.fabricCanvas.on("selection:cleared", (event) => this.onFabricSelectionChanged(event));
|
|
3941
4054
|
this.fabricCanvas.on("text:editing:entered", () => this.onTextEditing());
|
|
3942
|
-
this.fabricCanvas.on("text:editing:exited", () => this.onTextEditing());
|
|
3943
4055
|
this.fabricCanvas.on("text:editing:exited", (event) => {
|
|
3944
4056
|
this.onTextEditing();
|
|
3945
4057
|
const target = event.target;
|
|
@@ -3948,8 +4060,6 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
|
|
|
3948
4060
|
}
|
|
3949
4061
|
});
|
|
3950
4062
|
this.fabricCanvas.on("text:changed", () => this.onTextChange());
|
|
3951
|
-
// Handle double-click on textbox to enter editing mode
|
|
3952
|
-
// @ts-ignore
|
|
3953
4063
|
this.fabricCanvas.on("mouse:dblclick", (event) => this.onDoubleClick(event));
|
|
3954
4064
|
}
|
|
3955
4065
|
/**
|
|
@@ -3984,29 +4094,20 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
|
|
|
3984
4094
|
selection.setEvent(event);
|
|
3985
4095
|
}
|
|
3986
4096
|
this.selection$.next(selection);
|
|
3987
|
-
this.enterTextSelectionModeIfRequired();
|
|
3988
|
-
}
|
|
3989
|
-
enterTextSelectionModeIfRequired() {
|
|
3990
|
-
var _a, _b;
|
|
3991
|
-
if (!this.profile$.value.enterTextEditImmediatelyOnSelect && !this.layoutManager$.value.enterTextEditImmediatelyOnSelect)
|
|
3992
|
-
return;
|
|
3993
|
-
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)
|
|
3994
|
-
return;
|
|
3995
|
-
const text = this.selection$.value.objects[0];
|
|
3996
|
-
text.enterEditing();
|
|
3997
4097
|
}
|
|
3998
4098
|
onTextEditing() {
|
|
3999
4099
|
const selected = this.getFieldsByCanvasObject(this.fabricCanvas.getActiveObjects());
|
|
4000
4100
|
this.selection$.next(new SelectionData(selected));
|
|
4001
4101
|
}
|
|
4002
4102
|
onDoubleClick(event) {
|
|
4103
|
+
var _a, _b;
|
|
4003
4104
|
const target = event.target;
|
|
4004
4105
|
if (target && (target.type === 'textbox' || target.type === 'i-text')) {
|
|
4005
|
-
|
|
4106
|
+
const targetIText = target;
|
|
4107
|
+
if (!targetIText.isEditing) {
|
|
4006
4108
|
target.set({ editable: true, hoverCursor: 'text' });
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
this.enterTextSelectionModeIfRequired();
|
|
4109
|
+
(_a = targetIText.enterEditing) === null || _a === void 0 ? void 0 : _a.call(targetIText);
|
|
4110
|
+
(_b = targetIText.hiddenTextarea) === null || _b === void 0 ? void 0 : _b.focus();
|
|
4010
4111
|
}
|
|
4011
4112
|
}
|
|
4012
4113
|
}
|
|
@@ -10,6 +10,7 @@ export declare class AdvancedLayoutManager extends AbstractLayoutManager {
|
|
|
10
10
|
private repositionBackgrounds;
|
|
11
11
|
private resizeCanvas;
|
|
12
12
|
private stackObjects;
|
|
13
|
+
private dimensionsLayoutScheduled;
|
|
13
14
|
constructor(canvas: IInteractiveCanvas);
|
|
14
15
|
onCanvasDimensionsChange(): void;
|
|
15
16
|
onTextChange(): void;
|
|
@@ -205,6 +205,13 @@ export declare abstract class AbstractCanvasObject<T extends FabricObject> imple
|
|
|
205
205
|
protected onDelete(): void;
|
|
206
206
|
protected preventLeavingCanvas(): void;
|
|
207
207
|
protected getCanvasOverflow(): IOverflow;
|
|
208
|
+
protected getCanvasOverflowForBoundaryPrevention(): IOverflow;
|
|
209
|
+
protected getSimpleBoundingBox(): {
|
|
210
|
+
top: number;
|
|
211
|
+
left: number;
|
|
212
|
+
width: number;
|
|
213
|
+
height: number;
|
|
214
|
+
};
|
|
208
215
|
protected centerHorizontallyInternal(): void;
|
|
209
216
|
protected centerVerticallyInternal(): void;
|
|
210
217
|
protected nudgeInternal(x?: number, y?: number): void;
|
|
@@ -63,6 +63,8 @@ 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;
|
|
66
68
|
constructor(canvas: IInteractiveCanvas, fabricObject: fabric.Textbox, persistedField?: CanvasObjectData);
|
|
67
69
|
/**
|
|
68
70
|
* @override
|
|
@@ -321,4 +323,16 @@ export declare class Text extends AbstractCanvasObject<ExtendedFabricTextbox> im
|
|
|
321
323
|
private hasTextSelection;
|
|
322
324
|
private updateTextSelection;
|
|
323
325
|
private notifyCanvasOfTextChanges;
|
|
326
|
+
/**
|
|
327
|
+
* @override
|
|
328
|
+
* Provide a simpler bounding box for drag boundary checks that uses the textbox
|
|
329
|
+
* dimensions directly instead of getBoundingRect(), which includes ordered list
|
|
330
|
+
* markers that may extend far beyond the text and cause drag shaking issues.
|
|
331
|
+
*/
|
|
332
|
+
protected getSimpleBoundingBox(): {
|
|
333
|
+
top: number;
|
|
334
|
+
left: number;
|
|
335
|
+
width: number;
|
|
336
|
+
height: number;
|
|
337
|
+
};
|
|
324
338
|
}
|