@leapfrog/interactive-canvas 2.0.25-beta-13 → 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 +255 -138
- package/dist/interactive-canvas.js +255 -138
- package/dist/layout/advanced-layout-manager.d.ts +3 -0
- package/dist/layout/function/stack-objects.layout-function.d.ts +2 -1
- 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
|
|
|
@@ -252,38 +258,6 @@ class RepositionBackgroundsLayoutFunction {
|
|
|
252
258
|
}
|
|
253
259
|
}
|
|
254
260
|
|
|
255
|
-
class AdvancedLayoutManager extends AbstractLayoutManager {
|
|
256
|
-
constructor(canvas) {
|
|
257
|
-
super(canvas);
|
|
258
|
-
this.key = LayoutManager.ADVANCED;
|
|
259
|
-
this.pinToBottom = new PinToBottomLayoutFunction();
|
|
260
|
-
this.redrawAll = new RedrawAllLayoutFunction();
|
|
261
|
-
this.repositionBorders = new RepositionBordersLayoutFunction();
|
|
262
|
-
this.repositionCutoffs = new RepositionCutoffsLayoutFunction();
|
|
263
|
-
this.repositionBackgrounds = new RepositionBackgroundsLayoutFunction();
|
|
264
|
-
this.resizeCanvas = new ResizeCanvasLayoutFunction();
|
|
265
|
-
}
|
|
266
|
-
onCanvasDimensionsChange() {
|
|
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);
|
|
272
|
-
}
|
|
273
|
-
onTextChange() {
|
|
274
|
-
this.resizeCanvas.execute(this.canvas);
|
|
275
|
-
}
|
|
276
|
-
onObjectListChange() {
|
|
277
|
-
this.redrawAll.execute(this.canvas);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
class UpdateObjectDimensionsLayoutFunction {
|
|
282
|
-
execute(canvas) {
|
|
283
|
-
canvas.getObjectsInternal().forEach((object) => object.updateDimensions());
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
261
|
const MIN_MAGNITUDE = 1;
|
|
288
262
|
class StackObjectsLayoutFunction {
|
|
289
263
|
execute(canvas) {
|
|
@@ -296,7 +270,6 @@ class StackObjectsLayoutFunction {
|
|
|
296
270
|
const footerObjects = objects[0];
|
|
297
271
|
const bodyObjects = objects[1];
|
|
298
272
|
const bodyWidth = canvasDimensions.widthPx - canvasMargin.right - canvasMargin.left;
|
|
299
|
-
canvas.redraw();
|
|
300
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.
|
|
301
274
|
this.applyWidths(allObjects, bodyWidth);
|
|
302
275
|
const bodyRows = this.getRows(bodyObjects);
|
|
@@ -383,7 +356,11 @@ class StackObjectsLayoutFunction {
|
|
|
383
356
|
object.setPosition({
|
|
384
357
|
width: actualWidth
|
|
385
358
|
});
|
|
386
|
-
|
|
359
|
+
});
|
|
360
|
+
_(objects).forEach((object) => {
|
|
361
|
+
if (object.getType() !== ObjectType.VERTICAL_RULE) {
|
|
362
|
+
object.updateDimensions();
|
|
363
|
+
}
|
|
387
364
|
});
|
|
388
365
|
}
|
|
389
366
|
adjustCanvasSize(canvas, requiredHeight) {
|
|
@@ -444,6 +421,73 @@ class StackObjectsLayoutFunction {
|
|
|
444
421
|
}
|
|
445
422
|
}
|
|
446
423
|
|
|
424
|
+
var ObjectType;
|
|
425
|
+
(function (ObjectType) {
|
|
426
|
+
ObjectType["TEXT"] = "TEXT";
|
|
427
|
+
ObjectType["BORDER"] = "BORDER";
|
|
428
|
+
ObjectType["IMAGE"] = "IMAGE";
|
|
429
|
+
ObjectType["RECTANGLE"] = "RECTANGLE";
|
|
430
|
+
ObjectType["LIBRARY_IMAGE"] = "LIBRARY_IMAGE";
|
|
431
|
+
ObjectType["CUTOFF"] = "CUTOFF";
|
|
432
|
+
ObjectType["VERTICAL_RULE"] = "VERTICAL_RULE";
|
|
433
|
+
ObjectType["HORIZONTAL_RULE"] = "HORIZONTAL_RULE";
|
|
434
|
+
ObjectType["RICH_TEXT"] = "RICH_TEXT";
|
|
435
|
+
ObjectType["BACKGROUND_IMAGE"] = "BACKGROUND_IMAGE";
|
|
436
|
+
})(ObjectType || (ObjectType = {}));
|
|
437
|
+
|
|
438
|
+
class AdvancedLayoutManager extends AbstractLayoutManager {
|
|
439
|
+
constructor(canvas) {
|
|
440
|
+
super(canvas);
|
|
441
|
+
this.key = LayoutManager.ADVANCED;
|
|
442
|
+
this.pinToBottom = new PinToBottomLayoutFunction();
|
|
443
|
+
this.redrawAll = new RedrawAllLayoutFunction();
|
|
444
|
+
this.repositionBorders = new RepositionBordersLayoutFunction();
|
|
445
|
+
this.repositionCutoffs = new RepositionCutoffsLayoutFunction();
|
|
446
|
+
this.repositionBackgrounds = new RepositionBackgroundsLayoutFunction();
|
|
447
|
+
this.resizeCanvas = new ResizeCanvasLayoutFunction();
|
|
448
|
+
this.stackObjects = new StackObjectsLayoutFunction();
|
|
449
|
+
this.dimensionsLayoutScheduled = false;
|
|
450
|
+
}
|
|
451
|
+
onCanvasDimensionsChange() {
|
|
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
|
+
});
|
|
465
|
+
}
|
|
466
|
+
onTextChange() {
|
|
467
|
+
this.resizeCanvas.execute(this.canvas);
|
|
468
|
+
}
|
|
469
|
+
onObjectListChange() {
|
|
470
|
+
this.redrawAll.execute(this.canvas);
|
|
471
|
+
}
|
|
472
|
+
dimensionChange() {
|
|
473
|
+
const canvasMargin = this.canvas.getMargin();
|
|
474
|
+
const canvasDimensions = this.canvas.getDimensions();
|
|
475
|
+
const allObjects = this.canvas.getObjectsByTypesInternal(ObjectType.TEXT, ObjectType.RICH_TEXT,
|
|
476
|
+
//ObjectType.IMAGE,
|
|
477
|
+
ObjectType.RECTANGLE, ObjectType.VERTICAL_RULE, ObjectType.HORIZONTAL_RULE);
|
|
478
|
+
const bodyWidth = canvasDimensions.widthPx - canvasMargin.right - canvasMargin.left;
|
|
479
|
+
this.canvas.redraw();
|
|
480
|
+
//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.
|
|
481
|
+
this.stackObjects.applyWidths(allObjects, bodyWidth);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
class UpdateObjectDimensionsLayoutFunction {
|
|
486
|
+
execute(canvas) {
|
|
487
|
+
canvas.getObjectsInternal().forEach((object) => object.updateDimensions());
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
447
491
|
class StandardLayoutManager extends AbstractLayoutManager {
|
|
448
492
|
constructor(canvas) {
|
|
449
493
|
super(canvas);
|
|
@@ -456,6 +500,7 @@ class StandardLayoutManager extends AbstractLayoutManager {
|
|
|
456
500
|
this.repositionBackgrounds = new RepositionBackgroundsLayoutFunction();
|
|
457
501
|
this.stackObjects = new StackObjectsLayoutFunction();
|
|
458
502
|
this.updateObjectDimensions = new UpdateObjectDimensionsLayoutFunction();
|
|
503
|
+
this.layoutBatchScheduled = false;
|
|
459
504
|
}
|
|
460
505
|
/**
|
|
461
506
|
* @override
|
|
@@ -497,12 +542,19 @@ class StandardLayoutManager extends AbstractLayoutManager {
|
|
|
497
542
|
this.stackAndRedraw();
|
|
498
543
|
}
|
|
499
544
|
stackAndRedraw() {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
this.
|
|
504
|
-
|
|
505
|
-
|
|
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
|
+
});
|
|
506
558
|
}
|
|
507
559
|
}
|
|
508
560
|
|
|
@@ -969,7 +1021,7 @@ class AbstractCanvasObject {
|
|
|
969
1021
|
//no-op
|
|
970
1022
|
}
|
|
971
1023
|
preventLeavingCanvas() {
|
|
972
|
-
const overflow = this.
|
|
1024
|
+
const overflow = this.getCanvasOverflowForBoundaryPrevention();
|
|
973
1025
|
if (overflow.hasOverflow) {
|
|
974
1026
|
const adjustX = overflow.left || -overflow.right;
|
|
975
1027
|
const adjustY = overflow.top || -overflow.bottom;
|
|
@@ -995,6 +1047,29 @@ class AbstractCanvasObject {
|
|
|
995
1047
|
hasOverflow: top > 0 || right > 0 || bottom > 0 || left > 0
|
|
996
1048
|
};
|
|
997
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
|
+
}
|
|
998
1073
|
centerHorizontallyInternal() {
|
|
999
1074
|
const canvasWidth = this.canvas.getDimensions().widthPx;
|
|
1000
1075
|
const objectWidth = this.getDimensions().width.px;
|
|
@@ -1042,7 +1117,8 @@ class AbstractCanvasObject {
|
|
|
1042
1117
|
mt: options.allowScale,
|
|
1043
1118
|
mr: options.allowScale,
|
|
1044
1119
|
mb: options.allowScale,
|
|
1045
|
-
mtr: options.allowRotate
|
|
1120
|
+
mtr: options.allowRotate,
|
|
1121
|
+
// dragControl: options.allowMove // Show drag control when movement is allowed
|
|
1046
1122
|
});
|
|
1047
1123
|
}
|
|
1048
1124
|
}
|
|
@@ -1763,32 +1839,32 @@ class RichText extends Image {
|
|
|
1763
1839
|
}
|
|
1764
1840
|
}
|
|
1765
1841
|
|
|
1766
|
-
var ObjectType;
|
|
1767
|
-
(function (ObjectType) {
|
|
1768
|
-
ObjectType["TEXT"] = "TEXT";
|
|
1769
|
-
ObjectType["BORDER"] = "BORDER";
|
|
1770
|
-
ObjectType["IMAGE"] = "IMAGE";
|
|
1771
|
-
ObjectType["RECTANGLE"] = "RECTANGLE";
|
|
1772
|
-
ObjectType["LIBRARY_IMAGE"] = "LIBRARY_IMAGE";
|
|
1773
|
-
ObjectType["CUTOFF"] = "CUTOFF";
|
|
1774
|
-
ObjectType["VERTICAL_RULE"] = "VERTICAL_RULE";
|
|
1775
|
-
ObjectType["HORIZONTAL_RULE"] = "HORIZONTAL_RULE";
|
|
1776
|
-
ObjectType["RICH_TEXT"] = "RICH_TEXT";
|
|
1777
|
-
ObjectType["BACKGROUND_IMAGE"] = "BACKGROUND_IMAGE";
|
|
1778
|
-
})(ObjectType || (ObjectType = {}));
|
|
1779
|
-
|
|
1780
1842
|
class Text extends AbstractCanvasObject {
|
|
1781
1843
|
constructor(canvas, fabricObject, persistedField) {
|
|
1782
1844
|
super(ObjectType.TEXT, canvas, fabricObject, persistedField);
|
|
1845
|
+
this.notifyCanvasTimeout = null;
|
|
1846
|
+
this.NOTIFY_DELAY_MS = 150; // Debounce canvas notifications
|
|
1783
1847
|
this.canvas = canvas;
|
|
1784
1848
|
this.fontLibrary = canvas.getFontLibrary();
|
|
1785
1849
|
this.baseComponentHeight = this.fabricObject.height;
|
|
1786
1850
|
this.text$ = new BehaviorSubject(this.fabricObject.text);
|
|
1787
1851
|
this.data.userText = this.fabricObject.text;
|
|
1788
1852
|
this.textSelection$ = new BehaviorSubject(new TextSelection(this.fabricObject, this.fontLibrary));
|
|
1789
|
-
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
|
+
});
|
|
1790
1858
|
this.fabricObject.on("changed", () => this.onTextChange());
|
|
1791
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
|
+
});
|
|
1792
1868
|
this.applyBulletListMonkeyPatches();
|
|
1793
1869
|
}
|
|
1794
1870
|
/**
|
|
@@ -2140,11 +2216,25 @@ class Text extends AbstractCanvasObject {
|
|
|
2140
2216
|
getBulletMargin() {
|
|
2141
2217
|
if (!this.isBulleted())
|
|
2142
2218
|
return 0;
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
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
|
+
}
|
|
2148
2238
|
}
|
|
2149
2239
|
/**
|
|
2150
2240
|
* @override
|
|
@@ -2154,10 +2244,35 @@ class Text extends AbstractCanvasObject {
|
|
|
2154
2244
|
if (!this.isOrderedList())
|
|
2155
2245
|
return 0;
|
|
2156
2246
|
const orderedListMarkerEntities = this.fabricObject._orderedListMarkerEntities;
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
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
|
+
}
|
|
2161
2276
|
}
|
|
2162
2277
|
/**
|
|
2163
2278
|
* @override
|
|
@@ -2398,6 +2513,7 @@ class Text extends AbstractCanvasObject {
|
|
|
2398
2513
|
lockMovementY: true,
|
|
2399
2514
|
hasControls: false,
|
|
2400
2515
|
selectable: false,
|
|
2516
|
+
// evented: false, // Don't capture mouse events - allows clicks to pass through to textbox
|
|
2401
2517
|
originX: "left",
|
|
2402
2518
|
originY: "top"
|
|
2403
2519
|
});
|
|
@@ -2492,7 +2608,7 @@ class Text extends AbstractCanvasObject {
|
|
|
2492
2608
|
text: `${lineNumber}.`
|
|
2493
2609
|
});
|
|
2494
2610
|
}
|
|
2495
|
-
if (method === "fillText" && this.fabricObject.left <= 3) {
|
|
2611
|
+
if (method === "fillText" && this.isOrderedList() && this.fabricObject.left <= 3) {
|
|
2496
2612
|
left = left + this.getOrderedListMargin();
|
|
2497
2613
|
}
|
|
2498
2614
|
this.fabricObject.dirty = true;
|
|
@@ -2504,13 +2620,24 @@ class Text extends AbstractCanvasObject {
|
|
|
2504
2620
|
* @inheritDoc
|
|
2505
2621
|
*/
|
|
2506
2622
|
updateFabricObjectBehavior() {
|
|
2623
|
+
// DON'T call super.updateFabricObjectBehavior() for textboxes
|
|
2624
|
+
// because it locks movement which interferes with text editing in Fabric v7
|
|
2507
2625
|
super.updateFabricObjectBehavior();
|
|
2508
2626
|
const profile = this.canvas.getProfile();
|
|
2509
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
|
|
2510
2630
|
this.fabricObject.set({
|
|
2631
|
+
lockMovementX: false, // Allow dragging
|
|
2632
|
+
lockMovementY: false, // Allow dragging
|
|
2511
2633
|
lockScalingY: true,
|
|
2512
2634
|
lockScalingX: this.isPinToBottom(),
|
|
2513
|
-
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",
|
|
2514
2641
|
});
|
|
2515
2642
|
this.fabricObject.setControlsVisibility({
|
|
2516
2643
|
tl: false,
|
|
@@ -2520,35 +2647,64 @@ class Text extends AbstractCanvasObject {
|
|
|
2520
2647
|
ml: allowManualPositioning && profile.allowScale && !this.isPinToBottom(),
|
|
2521
2648
|
mt: false,
|
|
2522
2649
|
mr: allowManualPositioning && profile.allowScale && !this.isPinToBottom(),
|
|
2523
|
-
mb: false
|
|
2650
|
+
mb: false,
|
|
2651
|
+
mtr: false,
|
|
2524
2652
|
});
|
|
2525
2653
|
this.redraw();
|
|
2526
2654
|
}
|
|
2527
2655
|
onTextChange() {
|
|
2528
2656
|
this.data.userText = this.fabricObject.text;
|
|
2529
2657
|
this.text$.next(this.fabricObject.text);
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
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
|
+
}
|
|
2533
2667
|
}
|
|
2534
2668
|
}
|
|
2535
2669
|
onDelete() {
|
|
2536
2670
|
this.removeBullets();
|
|
2537
2671
|
}
|
|
2538
2672
|
onSelectionChange() {
|
|
2539
|
-
this.updateTextSelection();
|
|
2673
|
+
this.updateTextSelection(false);
|
|
2540
2674
|
}
|
|
2541
2675
|
hasTextSelection() {
|
|
2542
2676
|
return this.canvas.getSelection().isText && this.textSelection$.value.length > 0;
|
|
2543
2677
|
}
|
|
2544
|
-
updateTextSelection() {
|
|
2678
|
+
updateTextSelection(notifyCanvas = true) {
|
|
2545
2679
|
if (this.canvas.headless)
|
|
2546
2680
|
return;
|
|
2547
2681
|
this.textSelection$.next(new TextSelection(this.fabricObject, this.fontLibrary));
|
|
2548
|
-
|
|
2682
|
+
if (notifyCanvas) {
|
|
2683
|
+
this.notifyCanvasOfTextChanges();
|
|
2684
|
+
}
|
|
2549
2685
|
}
|
|
2550
2686
|
notifyCanvasOfTextChanges() {
|
|
2551
|
-
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
|
+
};
|
|
2552
2708
|
}
|
|
2553
2709
|
}
|
|
2554
2710
|
Text.NEW_LINE = "\n";
|
|
@@ -2714,6 +2870,7 @@ class AbstractInteractiveCanvas {
|
|
|
2714
2870
|
this.devicePixelRatio = devicePixelRatio;
|
|
2715
2871
|
this.fontLibrary = new FontLibrary();
|
|
2716
2872
|
this.dimensionChangeRejections$ = new Subject();
|
|
2873
|
+
this.redrawScheduled = false;
|
|
2717
2874
|
this.IMAGE_CAPTURE_COEFFICIENT = this.calculateImageCaptureCoefficient();
|
|
2718
2875
|
/**
|
|
2719
2876
|
* Fabric defaults to only 2 decimal places when saving, which causes havoc on the scaling of large images.
|
|
@@ -2735,8 +2892,8 @@ class AbstractInteractiveCanvas {
|
|
|
2735
2892
|
//Configure canvas behaviour
|
|
2736
2893
|
this.fabricCanvas.stateful = true;
|
|
2737
2894
|
this.fabricCanvas.preserveObjectStacking = true;
|
|
2738
|
-
this.fabricCanvas.perPixelTargetFind =
|
|
2739
|
-
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)
|
|
2740
2897
|
this.setBackgroundColor("#FFFFFF");
|
|
2741
2898
|
}
|
|
2742
2899
|
/**
|
|
@@ -3040,7 +3197,6 @@ class AbstractInteractiveCanvas {
|
|
|
3040
3197
|
*/
|
|
3041
3198
|
addImageObject(imageUrl) {
|
|
3042
3199
|
return __awaiter(this, void 0, void 0, function* () {
|
|
3043
|
-
// Don't let the image be more than half the canvas in size.
|
|
3044
3200
|
const maxSizeScale = 2;
|
|
3045
3201
|
const maxWidth = this.dimensions$.value.widthPx / maxSizeScale;
|
|
3046
3202
|
const options = { crossOrigin: "anonymous" };
|
|
@@ -3052,7 +3208,6 @@ class AbstractInteractiveCanvas {
|
|
|
3052
3208
|
if (curWidth > maxWidth) {
|
|
3053
3209
|
newScale = maxWidth / curWidth;
|
|
3054
3210
|
}
|
|
3055
|
-
// Set image properties and add to the canvas
|
|
3056
3211
|
const fabricImage = new this.fabric.Image(image, {
|
|
3057
3212
|
id: id,
|
|
3058
3213
|
scaleX: newScale,
|
|
@@ -3072,7 +3227,6 @@ class AbstractInteractiveCanvas {
|
|
|
3072
3227
|
return object;
|
|
3073
3228
|
}
|
|
3074
3229
|
else {
|
|
3075
|
-
// Browser-side logic
|
|
3076
3230
|
return yield new Promise((resolve) => {
|
|
3077
3231
|
this.fabric.Image.fromURL(imageUrl, options).then((fabricImage) => {
|
|
3078
3232
|
const id = this.randomId();
|
|
@@ -3124,15 +3278,14 @@ class AbstractInteractiveCanvas {
|
|
|
3124
3278
|
fontFamily: profile.defaultFont,
|
|
3125
3279
|
textAlign: profile.defaultTextAlignment,
|
|
3126
3280
|
hoverCursor: "move",
|
|
3127
|
-
moveCursor:
|
|
3281
|
+
moveCursor: "move",
|
|
3282
|
+
editable: false,
|
|
3283
|
+
editingBorderColor: '#2196F3',
|
|
3128
3284
|
multiLine: true,
|
|
3129
3285
|
lineHeight: profile.defaultLineHeight,
|
|
3130
3286
|
charSpacing: profile.defaultCharacterSpacing,
|
|
3131
|
-
perPixelTargetFind: false,
|
|
3132
|
-
targetFindTolerance: 4,
|
|
3133
3287
|
splitByGrapheme: true,
|
|
3134
|
-
|
|
3135
|
-
padding: 0,
|
|
3288
|
+
padding: 0, // Increased from 0 for better hover detection in Fabric v7
|
|
3136
3289
|
cornerSize: 8,
|
|
3137
3290
|
touchCornerSize: 12
|
|
3138
3291
|
};
|
|
@@ -3581,9 +3734,17 @@ class AbstractInteractiveCanvas {
|
|
|
3581
3734
|
}
|
|
3582
3735
|
/**
|
|
3583
3736
|
* Redraw the canvas.
|
|
3737
|
+
* Uses requestAnimationFrame to batch multiple redraw calls into a single render.
|
|
3584
3738
|
*/
|
|
3585
3739
|
redraw() {
|
|
3586
|
-
this.
|
|
3740
|
+
if (this.redrawScheduled) {
|
|
3741
|
+
return;
|
|
3742
|
+
}
|
|
3743
|
+
this.redrawScheduled = true;
|
|
3744
|
+
requestAnimationFrame(() => {
|
|
3745
|
+
this.fabricCanvas.requestRenderAll();
|
|
3746
|
+
this.redrawScheduled = false;
|
|
3747
|
+
});
|
|
3587
3748
|
}
|
|
3588
3749
|
syncObjectList() {
|
|
3589
3750
|
const newObjectList = _(this.fabricCanvas.getObjects())
|
|
@@ -3709,7 +3870,6 @@ class AbstractInteractiveCanvas {
|
|
|
3709
3870
|
* Update the actual dimensions of the canvas by multiplying the requested dimensions by the current zoom factor.
|
|
3710
3871
|
*/
|
|
3711
3872
|
updateCanvasDimensions(dims, zoom) {
|
|
3712
|
-
this.fabricCanvas.getWidth();
|
|
3713
3873
|
const width = dims.widthPx * zoom;
|
|
3714
3874
|
const height = dims.heightPx * zoom;
|
|
3715
3875
|
this.fabricCanvas.setZoom(zoom);
|
|
@@ -3717,37 +3877,6 @@ class AbstractInteractiveCanvas {
|
|
|
3717
3877
|
width,
|
|
3718
3878
|
height
|
|
3719
3879
|
});
|
|
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
|
-
}*/
|
|
3751
3880
|
}
|
|
3752
3881
|
onProfileChanged(profile) {
|
|
3753
3882
|
this.setMultiSelect(profile.allowMultiSelect);
|
|
@@ -3902,7 +4031,6 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
|
|
|
3902
4031
|
this.fabricCanvas.on("selection:created", (event) => this.onFabricSelectionChanged(event));
|
|
3903
4032
|
this.fabricCanvas.on("selection:cleared", (event) => this.onFabricSelectionChanged(event));
|
|
3904
4033
|
this.fabricCanvas.on("text:editing:entered", () => this.onTextEditing());
|
|
3905
|
-
this.fabricCanvas.on("text:editing:exited", () => this.onTextEditing());
|
|
3906
4034
|
this.fabricCanvas.on("text:editing:exited", (event) => {
|
|
3907
4035
|
this.onTextEditing();
|
|
3908
4036
|
const target = event.target;
|
|
@@ -3911,8 +4039,6 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
|
|
|
3911
4039
|
}
|
|
3912
4040
|
});
|
|
3913
4041
|
this.fabricCanvas.on("text:changed", () => this.onTextChange());
|
|
3914
|
-
// Handle double-click on textbox to enter editing mode
|
|
3915
|
-
// @ts-ignore
|
|
3916
4042
|
this.fabricCanvas.on("mouse:dblclick", (event) => this.onDoubleClick(event));
|
|
3917
4043
|
}
|
|
3918
4044
|
/**
|
|
@@ -3947,29 +4073,20 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
|
|
|
3947
4073
|
selection.setEvent(event);
|
|
3948
4074
|
}
|
|
3949
4075
|
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();
|
|
3960
4076
|
}
|
|
3961
4077
|
onTextEditing() {
|
|
3962
4078
|
const selected = this.getFieldsByCanvasObject(this.fabricCanvas.getActiveObjects());
|
|
3963
4079
|
this.selection$.next(new SelectionData(selected));
|
|
3964
4080
|
}
|
|
3965
4081
|
onDoubleClick(event) {
|
|
4082
|
+
var _a, _b;
|
|
3966
4083
|
const target = event.target;
|
|
3967
4084
|
if (target && (target.type === 'textbox' || target.type === 'i-text')) {
|
|
3968
|
-
|
|
4085
|
+
const targetIText = target;
|
|
4086
|
+
if (!targetIText.isEditing) {
|
|
3969
4087
|
target.set({ editable: true, hoverCursor: 'text' });
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
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();
|
|
3973
4090
|
}
|
|
3974
4091
|
}
|
|
3975
4092
|
}
|