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