@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
|
@@ -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
|
|
|
@@ -273,38 +279,6 @@ class RepositionBackgroundsLayoutFunction {
|
|
|
273
279
|
}
|
|
274
280
|
}
|
|
275
281
|
|
|
276
|
-
class AdvancedLayoutManager extends AbstractLayoutManager {
|
|
277
|
-
constructor(canvas) {
|
|
278
|
-
super(canvas);
|
|
279
|
-
this.key = exports.LayoutManager.ADVANCED;
|
|
280
|
-
this.pinToBottom = new PinToBottomLayoutFunction();
|
|
281
|
-
this.redrawAll = new RedrawAllLayoutFunction();
|
|
282
|
-
this.repositionBorders = new RepositionBordersLayoutFunction();
|
|
283
|
-
this.repositionCutoffs = new RepositionCutoffsLayoutFunction();
|
|
284
|
-
this.repositionBackgrounds = new RepositionBackgroundsLayoutFunction();
|
|
285
|
-
this.resizeCanvas = new ResizeCanvasLayoutFunction();
|
|
286
|
-
}
|
|
287
|
-
onCanvasDimensionsChange() {
|
|
288
|
-
this.pinToBottom.execute(this.canvas);
|
|
289
|
-
this.repositionCutoffs.execute(this.canvas);
|
|
290
|
-
this.repositionBorders.execute(this.canvas);
|
|
291
|
-
this.repositionBackgrounds.execute(this.canvas);
|
|
292
|
-
this.redrawAll.execute(this.canvas);
|
|
293
|
-
}
|
|
294
|
-
onTextChange() {
|
|
295
|
-
this.resizeCanvas.execute(this.canvas);
|
|
296
|
-
}
|
|
297
|
-
onObjectListChange() {
|
|
298
|
-
this.redrawAll.execute(this.canvas);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
class UpdateObjectDimensionsLayoutFunction {
|
|
303
|
-
execute(canvas) {
|
|
304
|
-
canvas.getObjectsInternal().forEach((object) => object.updateDimensions());
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
282
|
const MIN_MAGNITUDE = 1;
|
|
309
283
|
class StackObjectsLayoutFunction {
|
|
310
284
|
execute(canvas) {
|
|
@@ -317,7 +291,6 @@ class StackObjectsLayoutFunction {
|
|
|
317
291
|
const footerObjects = objects[0];
|
|
318
292
|
const bodyObjects = objects[1];
|
|
319
293
|
const bodyWidth = canvasDimensions.widthPx - canvasMargin.right - canvasMargin.left;
|
|
320
|
-
canvas.redraw();
|
|
321
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.
|
|
322
295
|
this.applyWidths(allObjects, bodyWidth);
|
|
323
296
|
const bodyRows = this.getRows(bodyObjects);
|
|
@@ -404,7 +377,11 @@ class StackObjectsLayoutFunction {
|
|
|
404
377
|
object.setPosition({
|
|
405
378
|
width: actualWidth
|
|
406
379
|
});
|
|
407
|
-
|
|
380
|
+
});
|
|
381
|
+
_(objects).forEach((object) => {
|
|
382
|
+
if (object.getType() !== exports.ObjectType.VERTICAL_RULE) {
|
|
383
|
+
object.updateDimensions();
|
|
384
|
+
}
|
|
408
385
|
});
|
|
409
386
|
}
|
|
410
387
|
adjustCanvasSize(canvas, requiredHeight) {
|
|
@@ -465,6 +442,73 @@ class StackObjectsLayoutFunction {
|
|
|
465
442
|
}
|
|
466
443
|
}
|
|
467
444
|
|
|
445
|
+
exports.ObjectType = void 0;
|
|
446
|
+
(function (ObjectType) {
|
|
447
|
+
ObjectType["TEXT"] = "TEXT";
|
|
448
|
+
ObjectType["BORDER"] = "BORDER";
|
|
449
|
+
ObjectType["IMAGE"] = "IMAGE";
|
|
450
|
+
ObjectType["RECTANGLE"] = "RECTANGLE";
|
|
451
|
+
ObjectType["LIBRARY_IMAGE"] = "LIBRARY_IMAGE";
|
|
452
|
+
ObjectType["CUTOFF"] = "CUTOFF";
|
|
453
|
+
ObjectType["VERTICAL_RULE"] = "VERTICAL_RULE";
|
|
454
|
+
ObjectType["HORIZONTAL_RULE"] = "HORIZONTAL_RULE";
|
|
455
|
+
ObjectType["RICH_TEXT"] = "RICH_TEXT";
|
|
456
|
+
ObjectType["BACKGROUND_IMAGE"] = "BACKGROUND_IMAGE";
|
|
457
|
+
})(exports.ObjectType || (exports.ObjectType = {}));
|
|
458
|
+
|
|
459
|
+
class AdvancedLayoutManager extends AbstractLayoutManager {
|
|
460
|
+
constructor(canvas) {
|
|
461
|
+
super(canvas);
|
|
462
|
+
this.key = exports.LayoutManager.ADVANCED;
|
|
463
|
+
this.pinToBottom = new PinToBottomLayoutFunction();
|
|
464
|
+
this.redrawAll = new RedrawAllLayoutFunction();
|
|
465
|
+
this.repositionBorders = new RepositionBordersLayoutFunction();
|
|
466
|
+
this.repositionCutoffs = new RepositionCutoffsLayoutFunction();
|
|
467
|
+
this.repositionBackgrounds = new RepositionBackgroundsLayoutFunction();
|
|
468
|
+
this.resizeCanvas = new ResizeCanvasLayoutFunction();
|
|
469
|
+
this.stackObjects = new StackObjectsLayoutFunction();
|
|
470
|
+
this.dimensionsLayoutScheduled = false;
|
|
471
|
+
}
|
|
472
|
+
onCanvasDimensionsChange() {
|
|
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
|
+
});
|
|
486
|
+
}
|
|
487
|
+
onTextChange() {
|
|
488
|
+
this.resizeCanvas.execute(this.canvas);
|
|
489
|
+
}
|
|
490
|
+
onObjectListChange() {
|
|
491
|
+
this.redrawAll.execute(this.canvas);
|
|
492
|
+
}
|
|
493
|
+
dimensionChange() {
|
|
494
|
+
const canvasMargin = this.canvas.getMargin();
|
|
495
|
+
const canvasDimensions = this.canvas.getDimensions();
|
|
496
|
+
const allObjects = this.canvas.getObjectsByTypesInternal(exports.ObjectType.TEXT, exports.ObjectType.RICH_TEXT,
|
|
497
|
+
//ObjectType.IMAGE,
|
|
498
|
+
exports.ObjectType.RECTANGLE, exports.ObjectType.VERTICAL_RULE, exports.ObjectType.HORIZONTAL_RULE);
|
|
499
|
+
const bodyWidth = canvasDimensions.widthPx - canvasMargin.right - canvasMargin.left;
|
|
500
|
+
this.canvas.redraw();
|
|
501
|
+
//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.
|
|
502
|
+
this.stackObjects.applyWidths(allObjects, bodyWidth);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
class UpdateObjectDimensionsLayoutFunction {
|
|
507
|
+
execute(canvas) {
|
|
508
|
+
canvas.getObjectsInternal().forEach((object) => object.updateDimensions());
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
468
512
|
class StandardLayoutManager extends AbstractLayoutManager {
|
|
469
513
|
constructor(canvas) {
|
|
470
514
|
super(canvas);
|
|
@@ -477,6 +521,7 @@ class StandardLayoutManager extends AbstractLayoutManager {
|
|
|
477
521
|
this.repositionBackgrounds = new RepositionBackgroundsLayoutFunction();
|
|
478
522
|
this.stackObjects = new StackObjectsLayoutFunction();
|
|
479
523
|
this.updateObjectDimensions = new UpdateObjectDimensionsLayoutFunction();
|
|
524
|
+
this.layoutBatchScheduled = false;
|
|
480
525
|
}
|
|
481
526
|
/**
|
|
482
527
|
* @override
|
|
@@ -518,12 +563,19 @@ class StandardLayoutManager extends AbstractLayoutManager {
|
|
|
518
563
|
this.stackAndRedraw();
|
|
519
564
|
}
|
|
520
565
|
stackAndRedraw() {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
this.
|
|
525
|
-
|
|
526
|
-
|
|
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
|
+
});
|
|
527
579
|
}
|
|
528
580
|
}
|
|
529
581
|
|
|
@@ -990,7 +1042,7 @@ class AbstractCanvasObject {
|
|
|
990
1042
|
//no-op
|
|
991
1043
|
}
|
|
992
1044
|
preventLeavingCanvas() {
|
|
993
|
-
const overflow = this.
|
|
1045
|
+
const overflow = this.getCanvasOverflowForBoundaryPrevention();
|
|
994
1046
|
if (overflow.hasOverflow) {
|
|
995
1047
|
const adjustX = overflow.left || -overflow.right;
|
|
996
1048
|
const adjustY = overflow.top || -overflow.bottom;
|
|
@@ -1016,6 +1068,29 @@ class AbstractCanvasObject {
|
|
|
1016
1068
|
hasOverflow: top > 0 || right > 0 || bottom > 0 || left > 0
|
|
1017
1069
|
};
|
|
1018
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
|
+
}
|
|
1019
1094
|
centerHorizontallyInternal() {
|
|
1020
1095
|
const canvasWidth = this.canvas.getDimensions().widthPx;
|
|
1021
1096
|
const objectWidth = this.getDimensions().width.px;
|
|
@@ -1063,7 +1138,8 @@ class AbstractCanvasObject {
|
|
|
1063
1138
|
mt: options.allowScale,
|
|
1064
1139
|
mr: options.allowScale,
|
|
1065
1140
|
mb: options.allowScale,
|
|
1066
|
-
mtr: options.allowRotate
|
|
1141
|
+
mtr: options.allowRotate,
|
|
1142
|
+
// dragControl: options.allowMove // Show drag control when movement is allowed
|
|
1067
1143
|
});
|
|
1068
1144
|
}
|
|
1069
1145
|
}
|
|
@@ -1784,32 +1860,32 @@ class RichText extends Image {
|
|
|
1784
1860
|
}
|
|
1785
1861
|
}
|
|
1786
1862
|
|
|
1787
|
-
exports.ObjectType = void 0;
|
|
1788
|
-
(function (ObjectType) {
|
|
1789
|
-
ObjectType["TEXT"] = "TEXT";
|
|
1790
|
-
ObjectType["BORDER"] = "BORDER";
|
|
1791
|
-
ObjectType["IMAGE"] = "IMAGE";
|
|
1792
|
-
ObjectType["RECTANGLE"] = "RECTANGLE";
|
|
1793
|
-
ObjectType["LIBRARY_IMAGE"] = "LIBRARY_IMAGE";
|
|
1794
|
-
ObjectType["CUTOFF"] = "CUTOFF";
|
|
1795
|
-
ObjectType["VERTICAL_RULE"] = "VERTICAL_RULE";
|
|
1796
|
-
ObjectType["HORIZONTAL_RULE"] = "HORIZONTAL_RULE";
|
|
1797
|
-
ObjectType["RICH_TEXT"] = "RICH_TEXT";
|
|
1798
|
-
ObjectType["BACKGROUND_IMAGE"] = "BACKGROUND_IMAGE";
|
|
1799
|
-
})(exports.ObjectType || (exports.ObjectType = {}));
|
|
1800
|
-
|
|
1801
1863
|
class Text extends AbstractCanvasObject {
|
|
1802
1864
|
constructor(canvas, fabricObject, persistedField) {
|
|
1803
1865
|
super(exports.ObjectType.TEXT, canvas, fabricObject, persistedField);
|
|
1866
|
+
this.notifyCanvasTimeout = null;
|
|
1867
|
+
this.NOTIFY_DELAY_MS = 150; // Debounce canvas notifications
|
|
1804
1868
|
this.canvas = canvas;
|
|
1805
1869
|
this.fontLibrary = canvas.getFontLibrary();
|
|
1806
1870
|
this.baseComponentHeight = this.fabricObject.height;
|
|
1807
1871
|
this.text$ = new rxjs.BehaviorSubject(this.fabricObject.text);
|
|
1808
1872
|
this.data.userText = this.fabricObject.text;
|
|
1809
1873
|
this.textSelection$ = new rxjs.BehaviorSubject(new TextSelection(this.fabricObject, this.fontLibrary));
|
|
1810
|
-
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
|
+
});
|
|
1811
1879
|
this.fabricObject.on("changed", () => this.onTextChange());
|
|
1812
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
|
+
});
|
|
1813
1889
|
this.applyBulletListMonkeyPatches();
|
|
1814
1890
|
}
|
|
1815
1891
|
/**
|
|
@@ -2161,11 +2237,25 @@ class Text extends AbstractCanvasObject {
|
|
|
2161
2237
|
getBulletMargin() {
|
|
2162
2238
|
if (!this.isBulleted())
|
|
2163
2239
|
return 0;
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
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
|
+
}
|
|
2169
2259
|
}
|
|
2170
2260
|
/**
|
|
2171
2261
|
* @override
|
|
@@ -2175,10 +2265,35 @@ class Text extends AbstractCanvasObject {
|
|
|
2175
2265
|
if (!this.isOrderedList())
|
|
2176
2266
|
return 0;
|
|
2177
2267
|
const orderedListMarkerEntities = this.fabricObject._orderedListMarkerEntities;
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
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
|
+
}
|
|
2182
2297
|
}
|
|
2183
2298
|
/**
|
|
2184
2299
|
* @override
|
|
@@ -2419,6 +2534,7 @@ class Text extends AbstractCanvasObject {
|
|
|
2419
2534
|
lockMovementY: true,
|
|
2420
2535
|
hasControls: false,
|
|
2421
2536
|
selectable: false,
|
|
2537
|
+
// evented: false, // Don't capture mouse events - allows clicks to pass through to textbox
|
|
2422
2538
|
originX: "left",
|
|
2423
2539
|
originY: "top"
|
|
2424
2540
|
});
|
|
@@ -2513,7 +2629,7 @@ class Text extends AbstractCanvasObject {
|
|
|
2513
2629
|
text: `${lineNumber}.`
|
|
2514
2630
|
});
|
|
2515
2631
|
}
|
|
2516
|
-
if (method === "fillText" && this.fabricObject.left <= 3) {
|
|
2632
|
+
if (method === "fillText" && this.isOrderedList() && this.fabricObject.left <= 3) {
|
|
2517
2633
|
left = left + this.getOrderedListMargin();
|
|
2518
2634
|
}
|
|
2519
2635
|
this.fabricObject.dirty = true;
|
|
@@ -2525,13 +2641,24 @@ class Text extends AbstractCanvasObject {
|
|
|
2525
2641
|
* @inheritDoc
|
|
2526
2642
|
*/
|
|
2527
2643
|
updateFabricObjectBehavior() {
|
|
2644
|
+
// DON'T call super.updateFabricObjectBehavior() for textboxes
|
|
2645
|
+
// because it locks movement which interferes with text editing in Fabric v7
|
|
2528
2646
|
super.updateFabricObjectBehavior();
|
|
2529
2647
|
const profile = this.canvas.getProfile();
|
|
2530
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
|
|
2531
2651
|
this.fabricObject.set({
|
|
2652
|
+
lockMovementX: false, // Allow dragging
|
|
2653
|
+
lockMovementY: false, // Allow dragging
|
|
2532
2654
|
lockScalingY: true,
|
|
2533
2655
|
lockScalingX: this.isPinToBottom(),
|
|
2534
|
-
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",
|
|
2535
2662
|
});
|
|
2536
2663
|
this.fabricObject.setControlsVisibility({
|
|
2537
2664
|
tl: false,
|
|
@@ -2541,35 +2668,64 @@ class Text extends AbstractCanvasObject {
|
|
|
2541
2668
|
ml: allowManualPositioning && profile.allowScale && !this.isPinToBottom(),
|
|
2542
2669
|
mt: false,
|
|
2543
2670
|
mr: allowManualPositioning && profile.allowScale && !this.isPinToBottom(),
|
|
2544
|
-
mb: false
|
|
2671
|
+
mb: false,
|
|
2672
|
+
mtr: false,
|
|
2545
2673
|
});
|
|
2546
2674
|
this.redraw();
|
|
2547
2675
|
}
|
|
2548
2676
|
onTextChange() {
|
|
2549
2677
|
this.data.userText = this.fabricObject.text;
|
|
2550
2678
|
this.text$.next(this.fabricObject.text);
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
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
|
+
}
|
|
2554
2688
|
}
|
|
2555
2689
|
}
|
|
2556
2690
|
onDelete() {
|
|
2557
2691
|
this.removeBullets();
|
|
2558
2692
|
}
|
|
2559
2693
|
onSelectionChange() {
|
|
2560
|
-
this.updateTextSelection();
|
|
2694
|
+
this.updateTextSelection(false);
|
|
2561
2695
|
}
|
|
2562
2696
|
hasTextSelection() {
|
|
2563
2697
|
return this.canvas.getSelection().isText && this.textSelection$.value.length > 0;
|
|
2564
2698
|
}
|
|
2565
|
-
updateTextSelection() {
|
|
2699
|
+
updateTextSelection(notifyCanvas = true) {
|
|
2566
2700
|
if (this.canvas.headless)
|
|
2567
2701
|
return;
|
|
2568
2702
|
this.textSelection$.next(new TextSelection(this.fabricObject, this.fontLibrary));
|
|
2569
|
-
|
|
2703
|
+
if (notifyCanvas) {
|
|
2704
|
+
this.notifyCanvasOfTextChanges();
|
|
2705
|
+
}
|
|
2570
2706
|
}
|
|
2571
2707
|
notifyCanvasOfTextChanges() {
|
|
2572
|
-
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
|
+
};
|
|
2573
2729
|
}
|
|
2574
2730
|
}
|
|
2575
2731
|
Text.NEW_LINE = "\n";
|
|
@@ -2735,6 +2891,7 @@ class AbstractInteractiveCanvas {
|
|
|
2735
2891
|
this.devicePixelRatio = devicePixelRatio;
|
|
2736
2892
|
this.fontLibrary = new FontLibrary();
|
|
2737
2893
|
this.dimensionChangeRejections$ = new rxjs.Subject();
|
|
2894
|
+
this.redrawScheduled = false;
|
|
2738
2895
|
this.IMAGE_CAPTURE_COEFFICIENT = this.calculateImageCaptureCoefficient();
|
|
2739
2896
|
/**
|
|
2740
2897
|
* Fabric defaults to only 2 decimal places when saving, which causes havoc on the scaling of large images.
|
|
@@ -2756,8 +2913,8 @@ class AbstractInteractiveCanvas {
|
|
|
2756
2913
|
//Configure canvas behaviour
|
|
2757
2914
|
this.fabricCanvas.stateful = true;
|
|
2758
2915
|
this.fabricCanvas.preserveObjectStacking = true;
|
|
2759
|
-
this.fabricCanvas.perPixelTargetFind =
|
|
2760
|
-
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)
|
|
2761
2918
|
this.setBackgroundColor("#FFFFFF");
|
|
2762
2919
|
}
|
|
2763
2920
|
/**
|
|
@@ -3061,7 +3218,6 @@ class AbstractInteractiveCanvas {
|
|
|
3061
3218
|
*/
|
|
3062
3219
|
addImageObject(imageUrl) {
|
|
3063
3220
|
return __awaiter(this, void 0, void 0, function* () {
|
|
3064
|
-
// Don't let the image be more than half the canvas in size.
|
|
3065
3221
|
const maxSizeScale = 2;
|
|
3066
3222
|
const maxWidth = this.dimensions$.value.widthPx / maxSizeScale;
|
|
3067
3223
|
const options = { crossOrigin: "anonymous" };
|
|
@@ -3073,7 +3229,6 @@ class AbstractInteractiveCanvas {
|
|
|
3073
3229
|
if (curWidth > maxWidth) {
|
|
3074
3230
|
newScale = maxWidth / curWidth;
|
|
3075
3231
|
}
|
|
3076
|
-
// Set image properties and add to the canvas
|
|
3077
3232
|
const fabricImage = new this.fabric.Image(image, {
|
|
3078
3233
|
id: id,
|
|
3079
3234
|
scaleX: newScale,
|
|
@@ -3093,7 +3248,6 @@ class AbstractInteractiveCanvas {
|
|
|
3093
3248
|
return object;
|
|
3094
3249
|
}
|
|
3095
3250
|
else {
|
|
3096
|
-
// Browser-side logic
|
|
3097
3251
|
return yield new Promise((resolve) => {
|
|
3098
3252
|
this.fabric.Image.fromURL(imageUrl, options).then((fabricImage) => {
|
|
3099
3253
|
const id = this.randomId();
|
|
@@ -3145,15 +3299,14 @@ class AbstractInteractiveCanvas {
|
|
|
3145
3299
|
fontFamily: profile.defaultFont,
|
|
3146
3300
|
textAlign: profile.defaultTextAlignment,
|
|
3147
3301
|
hoverCursor: "move",
|
|
3148
|
-
moveCursor:
|
|
3302
|
+
moveCursor: "move",
|
|
3303
|
+
editable: false,
|
|
3304
|
+
editingBorderColor: '#2196F3',
|
|
3149
3305
|
multiLine: true,
|
|
3150
3306
|
lineHeight: profile.defaultLineHeight,
|
|
3151
3307
|
charSpacing: profile.defaultCharacterSpacing,
|
|
3152
|
-
perPixelTargetFind: false,
|
|
3153
|
-
targetFindTolerance: 4,
|
|
3154
3308
|
splitByGrapheme: true,
|
|
3155
|
-
|
|
3156
|
-
padding: 0,
|
|
3309
|
+
padding: 0, // Increased from 0 for better hover detection in Fabric v7
|
|
3157
3310
|
cornerSize: 8,
|
|
3158
3311
|
touchCornerSize: 12
|
|
3159
3312
|
};
|
|
@@ -3602,9 +3755,17 @@ class AbstractInteractiveCanvas {
|
|
|
3602
3755
|
}
|
|
3603
3756
|
/**
|
|
3604
3757
|
* Redraw the canvas.
|
|
3758
|
+
* Uses requestAnimationFrame to batch multiple redraw calls into a single render.
|
|
3605
3759
|
*/
|
|
3606
3760
|
redraw() {
|
|
3607
|
-
this.
|
|
3761
|
+
if (this.redrawScheduled) {
|
|
3762
|
+
return;
|
|
3763
|
+
}
|
|
3764
|
+
this.redrawScheduled = true;
|
|
3765
|
+
requestAnimationFrame(() => {
|
|
3766
|
+
this.fabricCanvas.requestRenderAll();
|
|
3767
|
+
this.redrawScheduled = false;
|
|
3768
|
+
});
|
|
3608
3769
|
}
|
|
3609
3770
|
syncObjectList() {
|
|
3610
3771
|
const newObjectList = _(this.fabricCanvas.getObjects())
|
|
@@ -3730,7 +3891,6 @@ class AbstractInteractiveCanvas {
|
|
|
3730
3891
|
* Update the actual dimensions of the canvas by multiplying the requested dimensions by the current zoom factor.
|
|
3731
3892
|
*/
|
|
3732
3893
|
updateCanvasDimensions(dims, zoom) {
|
|
3733
|
-
this.fabricCanvas.getWidth();
|
|
3734
3894
|
const width = dims.widthPx * zoom;
|
|
3735
3895
|
const height = dims.heightPx * zoom;
|
|
3736
3896
|
this.fabricCanvas.setZoom(zoom);
|
|
@@ -3738,37 +3898,6 @@ class AbstractInteractiveCanvas {
|
|
|
3738
3898
|
width,
|
|
3739
3899
|
height
|
|
3740
3900
|
});
|
|
3741
|
-
/* if (this.layoutManager$.value instanceof AdvancedLayoutManager) {
|
|
3742
|
-
const scaleFactor = width / oldWidth;
|
|
3743
|
-
this.fabricCanvas.calcOffset();
|
|
3744
|
-
const objects = this.fabricCanvas.getObjects();
|
|
3745
|
-
objects.forEach((obj: any) => {
|
|
3746
|
-
obj.set({
|
|
3747
|
-
left: obj.left * scaleFactor,
|
|
3748
|
-
});
|
|
3749
|
-
if (obj.type === "textbox" || obj.type === "text") {
|
|
3750
|
-
obj.set({
|
|
3751
|
-
scaleX: 1,
|
|
3752
|
-
scaleY: 1,
|
|
3753
|
-
width: obj.width * scaleFactor,
|
|
3754
|
-
});
|
|
3755
|
-
obj.initDimensions();
|
|
3756
|
-
} else if (obj.type === "border" || obj.type === "rect") {
|
|
3757
|
-
obj.set({
|
|
3758
|
-
width: obj.width * scaleFactor,
|
|
3759
|
-
scaleX: 1,
|
|
3760
|
-
strokeUniform: true,
|
|
3761
|
-
strokeAlign: "inner",
|
|
3762
|
-
});
|
|
3763
|
-
} else {
|
|
3764
|
-
obj.set({
|
|
3765
|
-
scaleX: obj.scaleX * scaleFactor,
|
|
3766
|
-
});
|
|
3767
|
-
}
|
|
3768
|
-
obj.setCoords();
|
|
3769
|
-
});
|
|
3770
|
-
this.fabricCanvas.requestRenderAll();
|
|
3771
|
-
}*/
|
|
3772
3901
|
}
|
|
3773
3902
|
onProfileChanged(profile) {
|
|
3774
3903
|
this.setMultiSelect(profile.allowMultiSelect);
|
|
@@ -3923,7 +4052,6 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
|
|
|
3923
4052
|
this.fabricCanvas.on("selection:created", (event) => this.onFabricSelectionChanged(event));
|
|
3924
4053
|
this.fabricCanvas.on("selection:cleared", (event) => this.onFabricSelectionChanged(event));
|
|
3925
4054
|
this.fabricCanvas.on("text:editing:entered", () => this.onTextEditing());
|
|
3926
|
-
this.fabricCanvas.on("text:editing:exited", () => this.onTextEditing());
|
|
3927
4055
|
this.fabricCanvas.on("text:editing:exited", (event) => {
|
|
3928
4056
|
this.onTextEditing();
|
|
3929
4057
|
const target = event.target;
|
|
@@ -3932,8 +4060,6 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
|
|
|
3932
4060
|
}
|
|
3933
4061
|
});
|
|
3934
4062
|
this.fabricCanvas.on("text:changed", () => this.onTextChange());
|
|
3935
|
-
// Handle double-click on textbox to enter editing mode
|
|
3936
|
-
// @ts-ignore
|
|
3937
4063
|
this.fabricCanvas.on("mouse:dblclick", (event) => this.onDoubleClick(event));
|
|
3938
4064
|
}
|
|
3939
4065
|
/**
|
|
@@ -3968,29 +4094,20 @@ class InteractiveCanvas extends AbstractInteractiveCanvas {
|
|
|
3968
4094
|
selection.setEvent(event);
|
|
3969
4095
|
}
|
|
3970
4096
|
this.selection$.next(selection);
|
|
3971
|
-
this.enterTextSelectionModeIfRequired();
|
|
3972
|
-
}
|
|
3973
|
-
enterTextSelectionModeIfRequired() {
|
|
3974
|
-
var _a, _b;
|
|
3975
|
-
if (!this.profile$.value.enterTextEditImmediatelyOnSelect && !this.layoutManager$.value.enterTextEditImmediatelyOnSelect)
|
|
3976
|
-
return;
|
|
3977
|
-
if (((_b = (_a = this.selection$.value) === null || _a === void 0 ? void 0 : _a.objects[0]) === null || _b === void 0 ? void 0 : _b.getType()) !== exports.ObjectType.TEXT)
|
|
3978
|
-
return;
|
|
3979
|
-
const text = this.selection$.value.objects[0];
|
|
3980
|
-
text.enterEditing();
|
|
3981
4097
|
}
|
|
3982
4098
|
onTextEditing() {
|
|
3983
4099
|
const selected = this.getFieldsByCanvasObject(this.fabricCanvas.getActiveObjects());
|
|
3984
4100
|
this.selection$.next(new SelectionData(selected));
|
|
3985
4101
|
}
|
|
3986
4102
|
onDoubleClick(event) {
|
|
4103
|
+
var _a, _b;
|
|
3987
4104
|
const target = event.target;
|
|
3988
4105
|
if (target && (target.type === 'textbox' || target.type === 'i-text')) {
|
|
3989
|
-
|
|
4106
|
+
const targetIText = target;
|
|
4107
|
+
if (!targetIText.isEditing) {
|
|
3990
4108
|
target.set({ editable: true, hoverCursor: 'text' });
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
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();
|
|
3994
4111
|
}
|
|
3995
4112
|
}
|
|
3996
4113
|
}
|
|
@@ -9,8 +9,11 @@ export declare class AdvancedLayoutManager extends AbstractLayoutManager {
|
|
|
9
9
|
private repositionCutoffs;
|
|
10
10
|
private repositionBackgrounds;
|
|
11
11
|
private resizeCanvas;
|
|
12
|
+
private stackObjects;
|
|
13
|
+
private dimensionsLayoutScheduled;
|
|
12
14
|
constructor(canvas: IInteractiveCanvas);
|
|
13
15
|
onCanvasDimensionsChange(): void;
|
|
14
16
|
onTextChange(): void;
|
|
15
17
|
onObjectListChange(): void;
|
|
18
|
+
private dimensionChange;
|
|
16
19
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { AbstractCanvasObject } from "../..";
|
|
1
2
|
import { IInteractiveCanvas } from "../../interactive-canvas.interface";
|
|
2
3
|
export declare class StackObjectsLayoutFunction {
|
|
3
4
|
execute(canvas: IInteractiveCanvas): void;
|
|
4
5
|
private layoutFooter;
|
|
5
6
|
private layoutBody;
|
|
6
|
-
|
|
7
|
+
applyWidths(objects: Array<AbstractCanvasObject<any>>, layoutWidth: number): void;
|
|
7
8
|
private adjustCanvasSize;
|
|
8
9
|
private getRows;
|
|
9
10
|
private getObjectHeight;
|