@leapfrog/interactive-canvas 2.0.25-beta-5 → 2.0.25-beta-6
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 +1 -0
- package/dist/interactive-canvas.es.js +79 -20
- package/dist/interactive-canvas.js +79 -20
- package/dist/layout/advanced-layout-manager.d.ts +1 -0
- package/dist/layout/standard-layout-manager.d.ts +1 -0
- package/dist/object/impl/text.d.ts +2 -0
- package/package.json +1 -1
|
@@ -340,6 +340,7 @@ 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.
|
|
345
346
|
*/
|
|
@@ -232,9 +232,18 @@ 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
|
-
|
|
235
|
+
// Only update coords for objects affected by height change (pinned to bottom, etc.)
|
|
236
|
+
// This is much faster than updating all objects on slow machines
|
|
237
|
+
canvas.getObjectsInternal().forEach((object) => {
|
|
238
|
+
var _a;
|
|
239
|
+
const canvasObj = canvas.getObjectsByTypeInternal(object.type).find(o => o.getId() === object.getId());
|
|
240
|
+
// Only update coords for objects that are positioned relative to canvas bottom or that might be affected
|
|
241
|
+
if (canvasObj && (((_a = canvasObj.isPinToBottom) === null || _a === void 0 ? void 0 : _a.call(canvasObj)) || object.top > currentDimensions.heightPx * 0.5)) {
|
|
242
|
+
object.setCoords();
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
canvas.redraw();
|
|
236
246
|
}
|
|
237
|
-
canvas.redraw();
|
|
238
247
|
}
|
|
239
248
|
}
|
|
240
249
|
|
|
@@ -262,13 +271,22 @@ class AdvancedLayoutManager extends AbstractLayoutManager {
|
|
|
262
271
|
this.repositionCutoffs = new RepositionCutoffsLayoutFunction();
|
|
263
272
|
this.repositionBackgrounds = new RepositionBackgroundsLayoutFunction();
|
|
264
273
|
this.resizeCanvas = new ResizeCanvasLayoutFunction();
|
|
274
|
+
this.dimensionsLayoutScheduled = false;
|
|
265
275
|
}
|
|
266
276
|
onCanvasDimensionsChange() {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
this.
|
|
277
|
+
// Batch dimension changes to avoid redundant layout passes
|
|
278
|
+
if (this.dimensionsLayoutScheduled) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
this.dimensionsLayoutScheduled = true;
|
|
282
|
+
Promise.resolve().then(() => {
|
|
283
|
+
this.pinToBottom.execute(this.canvas);
|
|
284
|
+
this.repositionCutoffs.execute(this.canvas);
|
|
285
|
+
this.repositionBorders.execute(this.canvas);
|
|
286
|
+
this.repositionBackgrounds.execute(this.canvas);
|
|
287
|
+
this.redrawAll.execute(this.canvas);
|
|
288
|
+
this.dimensionsLayoutScheduled = false;
|
|
289
|
+
});
|
|
272
290
|
}
|
|
273
291
|
onTextChange() {
|
|
274
292
|
this.resizeCanvas.execute(this.canvas);
|
|
@@ -296,7 +314,9 @@ class StackObjectsLayoutFunction {
|
|
|
296
314
|
const footerObjects = objects[0];
|
|
297
315
|
const bodyObjects = objects[1];
|
|
298
316
|
const bodyWidth = canvasDimensions.widthPx - canvasMargin.right - canvasMargin.left;
|
|
299
|
-
canvas.redraw();
|
|
317
|
+
//canvas.redraw();
|
|
318
|
+
// No need to redraw here - it will be called by the layout manager after all layout functions complete
|
|
319
|
+
// This reduces redundant render passes significantly
|
|
300
320
|
//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
321
|
this.applyWidths(allObjects, bodyWidth);
|
|
302
322
|
const bodyRows = this.getRows(bodyObjects);
|
|
@@ -383,7 +403,11 @@ class StackObjectsLayoutFunction {
|
|
|
383
403
|
object.setPosition({
|
|
384
404
|
width: actualWidth
|
|
385
405
|
});
|
|
386
|
-
|
|
406
|
+
_(objects).forEach((object) => {
|
|
407
|
+
if (object.getType() !== ObjectType.VERTICAL_RULE) {
|
|
408
|
+
object.updateDimensions();
|
|
409
|
+
}
|
|
410
|
+
});
|
|
387
411
|
});
|
|
388
412
|
}
|
|
389
413
|
adjustCanvasSize(canvas, requiredHeight) {
|
|
@@ -456,6 +480,7 @@ class StandardLayoutManager extends AbstractLayoutManager {
|
|
|
456
480
|
this.repositionBackgrounds = new RepositionBackgroundsLayoutFunction();
|
|
457
481
|
this.stackObjects = new StackObjectsLayoutFunction();
|
|
458
482
|
this.updateObjectDimensions = new UpdateObjectDimensionsLayoutFunction();
|
|
483
|
+
this.layoutBatchScheduled = false;
|
|
459
484
|
}
|
|
460
485
|
/**
|
|
461
486
|
* @override
|
|
@@ -497,12 +522,21 @@ class StandardLayoutManager extends AbstractLayoutManager {
|
|
|
497
522
|
this.stackAndRedraw();
|
|
498
523
|
}
|
|
499
524
|
stackAndRedraw() {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
this.
|
|
505
|
-
|
|
525
|
+
// Batch multiple layout requests into a single pass
|
|
526
|
+
if (this.layoutBatchScheduled) {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
this.layoutBatchScheduled = true;
|
|
530
|
+
// Use microtask to batch synchronous calls, but still execute before next frame
|
|
531
|
+
Promise.resolve().then(() => {
|
|
532
|
+
this.stackObjects.execute(this.canvas);
|
|
533
|
+
this.repositionCutoffs.execute(this.canvas);
|
|
534
|
+
this.repositionBorders.execute(this.canvas);
|
|
535
|
+
this.repositionBackgrounds.execute(this.canvas);
|
|
536
|
+
this.updateObjectDimensions.execute(this.canvas);
|
|
537
|
+
this.redrawAll.execute(this.canvas);
|
|
538
|
+
this.layoutBatchScheduled = false;
|
|
539
|
+
});
|
|
506
540
|
}
|
|
507
541
|
}
|
|
508
542
|
|
|
@@ -1780,6 +1814,9 @@ var ObjectType;
|
|
|
1780
1814
|
class Text extends AbstractCanvasObject {
|
|
1781
1815
|
constructor(canvas, fabricObject, persistedField) {
|
|
1782
1816
|
super(ObjectType.TEXT, canvas, fabricObject, persistedField);
|
|
1817
|
+
//private notifyCanvasTimeout: NodeJS.Timeout | null = null;
|
|
1818
|
+
this.notifyCanvasTimeout = null;
|
|
1819
|
+
this.NOTIFY_DELAY_MS = 150; // Debounce canvas notifications
|
|
1783
1820
|
this.canvas = canvas;
|
|
1784
1821
|
this.fontLibrary = canvas.getFontLibrary();
|
|
1785
1822
|
this.baseComponentHeight = this.fabricObject.height;
|
|
@@ -2527,9 +2564,15 @@ class Text extends AbstractCanvasObject {
|
|
|
2527
2564
|
onTextChange() {
|
|
2528
2565
|
this.data.userText = this.fabricObject.text;
|
|
2529
2566
|
this.text$.next(this.fabricObject.text);
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2567
|
+
// Only check width bounds if text is close to canvas edge (performance optimization)
|
|
2568
|
+
const currentLeft = this.fabricObject.left || 0;
|
|
2569
|
+
const canvasWidth = this.canvas.getDimensions().widthPx;
|
|
2570
|
+
// Skip expensive getBoundingBox if object is clearly within bounds
|
|
2571
|
+
if (currentLeft + (this.fabricObject.width || 0) > canvasWidth * 0.9) {
|
|
2572
|
+
if (this.getBoundingBox().width > canvasWidth) {
|
|
2573
|
+
this.fabricObject.set({ left: 0, width: canvasWidth });
|
|
2574
|
+
this.redraw();
|
|
2575
|
+
}
|
|
2533
2576
|
}
|
|
2534
2577
|
}
|
|
2535
2578
|
onDelete() {
|
|
@@ -2550,7 +2593,13 @@ class Text extends AbstractCanvasObject {
|
|
|
2550
2593
|
}
|
|
2551
2594
|
}
|
|
2552
2595
|
notifyCanvasOfTextChanges() {
|
|
2553
|
-
this.
|
|
2596
|
+
if (this.notifyCanvasTimeout) {
|
|
2597
|
+
clearTimeout(this.notifyCanvasTimeout);
|
|
2598
|
+
}
|
|
2599
|
+
this.notifyCanvasTimeout = setTimeout(() => {
|
|
2600
|
+
this.canvas.notifyTextChanged();
|
|
2601
|
+
this.notifyCanvasTimeout = null;
|
|
2602
|
+
}, this.NOTIFY_DELAY_MS);
|
|
2554
2603
|
}
|
|
2555
2604
|
}
|
|
2556
2605
|
Text.NEW_LINE = "\n";
|
|
@@ -2716,6 +2765,7 @@ class AbstractInteractiveCanvas {
|
|
|
2716
2765
|
this.devicePixelRatio = devicePixelRatio;
|
|
2717
2766
|
this.fontLibrary = new FontLibrary();
|
|
2718
2767
|
this.dimensionChangeRejections$ = new Subject();
|
|
2768
|
+
this.redrawScheduled = false;
|
|
2719
2769
|
this.IMAGE_CAPTURE_COEFFICIENT = this.calculateImageCaptureCoefficient();
|
|
2720
2770
|
/**
|
|
2721
2771
|
* Fabric defaults to only 2 decimal places when saving, which causes havoc on the scaling of large images.
|
|
@@ -3581,7 +3631,16 @@ class AbstractInteractiveCanvas {
|
|
|
3581
3631
|
* Redraw the canvas.
|
|
3582
3632
|
*/
|
|
3583
3633
|
redraw() {
|
|
3584
|
-
this.fabricCanvas.requestRenderAll();
|
|
3634
|
+
//this.fabricCanvas.requestRenderAll();
|
|
3635
|
+
if (this.redrawScheduled) {
|
|
3636
|
+
return; // Already scheduled, don't schedule again
|
|
3637
|
+
}
|
|
3638
|
+
this.redrawScheduled = true;
|
|
3639
|
+
// Use requestAnimationFrame for smoother rendering and automatic batching
|
|
3640
|
+
requestAnimationFrame(() => {
|
|
3641
|
+
this.fabricCanvas.requestRenderAll();
|
|
3642
|
+
this.redrawScheduled = false;
|
|
3643
|
+
});
|
|
3585
3644
|
}
|
|
3586
3645
|
syncObjectList() {
|
|
3587
3646
|
const newObjectList = _(this.fabricCanvas.getObjects())
|
|
@@ -253,9 +253,18 @@ 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
|
-
|
|
256
|
+
// Only update coords for objects affected by height change (pinned to bottom, etc.)
|
|
257
|
+
// This is much faster than updating all objects on slow machines
|
|
258
|
+
canvas.getObjectsInternal().forEach((object) => {
|
|
259
|
+
var _a;
|
|
260
|
+
const canvasObj = canvas.getObjectsByTypeInternal(object.type).find(o => o.getId() === object.getId());
|
|
261
|
+
// Only update coords for objects that are positioned relative to canvas bottom or that might be affected
|
|
262
|
+
if (canvasObj && (((_a = canvasObj.isPinToBottom) === null || _a === void 0 ? void 0 : _a.call(canvasObj)) || object.top > currentDimensions.heightPx * 0.5)) {
|
|
263
|
+
object.setCoords();
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
canvas.redraw();
|
|
257
267
|
}
|
|
258
|
-
canvas.redraw();
|
|
259
268
|
}
|
|
260
269
|
}
|
|
261
270
|
|
|
@@ -283,13 +292,22 @@ class AdvancedLayoutManager extends AbstractLayoutManager {
|
|
|
283
292
|
this.repositionCutoffs = new RepositionCutoffsLayoutFunction();
|
|
284
293
|
this.repositionBackgrounds = new RepositionBackgroundsLayoutFunction();
|
|
285
294
|
this.resizeCanvas = new ResizeCanvasLayoutFunction();
|
|
295
|
+
this.dimensionsLayoutScheduled = false;
|
|
286
296
|
}
|
|
287
297
|
onCanvasDimensionsChange() {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
this.
|
|
298
|
+
// Batch dimension changes to avoid redundant layout passes
|
|
299
|
+
if (this.dimensionsLayoutScheduled) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
this.dimensionsLayoutScheduled = true;
|
|
303
|
+
Promise.resolve().then(() => {
|
|
304
|
+
this.pinToBottom.execute(this.canvas);
|
|
305
|
+
this.repositionCutoffs.execute(this.canvas);
|
|
306
|
+
this.repositionBorders.execute(this.canvas);
|
|
307
|
+
this.repositionBackgrounds.execute(this.canvas);
|
|
308
|
+
this.redrawAll.execute(this.canvas);
|
|
309
|
+
this.dimensionsLayoutScheduled = false;
|
|
310
|
+
});
|
|
293
311
|
}
|
|
294
312
|
onTextChange() {
|
|
295
313
|
this.resizeCanvas.execute(this.canvas);
|
|
@@ -317,7 +335,9 @@ class StackObjectsLayoutFunction {
|
|
|
317
335
|
const footerObjects = objects[0];
|
|
318
336
|
const bodyObjects = objects[1];
|
|
319
337
|
const bodyWidth = canvasDimensions.widthPx - canvasMargin.right - canvasMargin.left;
|
|
320
|
-
canvas.redraw();
|
|
338
|
+
//canvas.redraw();
|
|
339
|
+
// No need to redraw here - it will be called by the layout manager after all layout functions complete
|
|
340
|
+
// This reduces redundant render passes significantly
|
|
321
341
|
//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
342
|
this.applyWidths(allObjects, bodyWidth);
|
|
323
343
|
const bodyRows = this.getRows(bodyObjects);
|
|
@@ -404,7 +424,11 @@ class StackObjectsLayoutFunction {
|
|
|
404
424
|
object.setPosition({
|
|
405
425
|
width: actualWidth
|
|
406
426
|
});
|
|
407
|
-
|
|
427
|
+
_(objects).forEach((object) => {
|
|
428
|
+
if (object.getType() !== exports.ObjectType.VERTICAL_RULE) {
|
|
429
|
+
object.updateDimensions();
|
|
430
|
+
}
|
|
431
|
+
});
|
|
408
432
|
});
|
|
409
433
|
}
|
|
410
434
|
adjustCanvasSize(canvas, requiredHeight) {
|
|
@@ -477,6 +501,7 @@ class StandardLayoutManager extends AbstractLayoutManager {
|
|
|
477
501
|
this.repositionBackgrounds = new RepositionBackgroundsLayoutFunction();
|
|
478
502
|
this.stackObjects = new StackObjectsLayoutFunction();
|
|
479
503
|
this.updateObjectDimensions = new UpdateObjectDimensionsLayoutFunction();
|
|
504
|
+
this.layoutBatchScheduled = false;
|
|
480
505
|
}
|
|
481
506
|
/**
|
|
482
507
|
* @override
|
|
@@ -518,12 +543,21 @@ class StandardLayoutManager extends AbstractLayoutManager {
|
|
|
518
543
|
this.stackAndRedraw();
|
|
519
544
|
}
|
|
520
545
|
stackAndRedraw() {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
this.
|
|
526
|
-
|
|
546
|
+
// Batch multiple layout requests into a single pass
|
|
547
|
+
if (this.layoutBatchScheduled) {
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
this.layoutBatchScheduled = true;
|
|
551
|
+
// Use microtask to batch synchronous calls, but still execute before next frame
|
|
552
|
+
Promise.resolve().then(() => {
|
|
553
|
+
this.stackObjects.execute(this.canvas);
|
|
554
|
+
this.repositionCutoffs.execute(this.canvas);
|
|
555
|
+
this.repositionBorders.execute(this.canvas);
|
|
556
|
+
this.repositionBackgrounds.execute(this.canvas);
|
|
557
|
+
this.updateObjectDimensions.execute(this.canvas);
|
|
558
|
+
this.redrawAll.execute(this.canvas);
|
|
559
|
+
this.layoutBatchScheduled = false;
|
|
560
|
+
});
|
|
527
561
|
}
|
|
528
562
|
}
|
|
529
563
|
|
|
@@ -1801,6 +1835,9 @@ exports.ObjectType = void 0;
|
|
|
1801
1835
|
class Text extends AbstractCanvasObject {
|
|
1802
1836
|
constructor(canvas, fabricObject, persistedField) {
|
|
1803
1837
|
super(exports.ObjectType.TEXT, canvas, fabricObject, persistedField);
|
|
1838
|
+
//private notifyCanvasTimeout: NodeJS.Timeout | null = null;
|
|
1839
|
+
this.notifyCanvasTimeout = null;
|
|
1840
|
+
this.NOTIFY_DELAY_MS = 150; // Debounce canvas notifications
|
|
1804
1841
|
this.canvas = canvas;
|
|
1805
1842
|
this.fontLibrary = canvas.getFontLibrary();
|
|
1806
1843
|
this.baseComponentHeight = this.fabricObject.height;
|
|
@@ -2548,9 +2585,15 @@ class Text extends AbstractCanvasObject {
|
|
|
2548
2585
|
onTextChange() {
|
|
2549
2586
|
this.data.userText = this.fabricObject.text;
|
|
2550
2587
|
this.text$.next(this.fabricObject.text);
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2588
|
+
// Only check width bounds if text is close to canvas edge (performance optimization)
|
|
2589
|
+
const currentLeft = this.fabricObject.left || 0;
|
|
2590
|
+
const canvasWidth = this.canvas.getDimensions().widthPx;
|
|
2591
|
+
// Skip expensive getBoundingBox if object is clearly within bounds
|
|
2592
|
+
if (currentLeft + (this.fabricObject.width || 0) > canvasWidth * 0.9) {
|
|
2593
|
+
if (this.getBoundingBox().width > canvasWidth) {
|
|
2594
|
+
this.fabricObject.set({ left: 0, width: canvasWidth });
|
|
2595
|
+
this.redraw();
|
|
2596
|
+
}
|
|
2554
2597
|
}
|
|
2555
2598
|
}
|
|
2556
2599
|
onDelete() {
|
|
@@ -2571,7 +2614,13 @@ class Text extends AbstractCanvasObject {
|
|
|
2571
2614
|
}
|
|
2572
2615
|
}
|
|
2573
2616
|
notifyCanvasOfTextChanges() {
|
|
2574
|
-
this.
|
|
2617
|
+
if (this.notifyCanvasTimeout) {
|
|
2618
|
+
clearTimeout(this.notifyCanvasTimeout);
|
|
2619
|
+
}
|
|
2620
|
+
this.notifyCanvasTimeout = setTimeout(() => {
|
|
2621
|
+
this.canvas.notifyTextChanged();
|
|
2622
|
+
this.notifyCanvasTimeout = null;
|
|
2623
|
+
}, this.NOTIFY_DELAY_MS);
|
|
2575
2624
|
}
|
|
2576
2625
|
}
|
|
2577
2626
|
Text.NEW_LINE = "\n";
|
|
@@ -2737,6 +2786,7 @@ class AbstractInteractiveCanvas {
|
|
|
2737
2786
|
this.devicePixelRatio = devicePixelRatio;
|
|
2738
2787
|
this.fontLibrary = new FontLibrary();
|
|
2739
2788
|
this.dimensionChangeRejections$ = new rxjs.Subject();
|
|
2789
|
+
this.redrawScheduled = false;
|
|
2740
2790
|
this.IMAGE_CAPTURE_COEFFICIENT = this.calculateImageCaptureCoefficient();
|
|
2741
2791
|
/**
|
|
2742
2792
|
* Fabric defaults to only 2 decimal places when saving, which causes havoc on the scaling of large images.
|
|
@@ -3602,7 +3652,16 @@ class AbstractInteractiveCanvas {
|
|
|
3602
3652
|
* Redraw the canvas.
|
|
3603
3653
|
*/
|
|
3604
3654
|
redraw() {
|
|
3605
|
-
this.fabricCanvas.requestRenderAll();
|
|
3655
|
+
//this.fabricCanvas.requestRenderAll();
|
|
3656
|
+
if (this.redrawScheduled) {
|
|
3657
|
+
return; // Already scheduled, don't schedule again
|
|
3658
|
+
}
|
|
3659
|
+
this.redrawScheduled = true;
|
|
3660
|
+
// Use requestAnimationFrame for smoother rendering and automatic batching
|
|
3661
|
+
requestAnimationFrame(() => {
|
|
3662
|
+
this.fabricCanvas.requestRenderAll();
|
|
3663
|
+
this.redrawScheduled = false;
|
|
3664
|
+
});
|
|
3606
3665
|
}
|
|
3607
3666
|
syncObjectList() {
|
|
3608
3667
|
const newObjectList = _(this.fabricCanvas.getObjects())
|
|
@@ -9,6 +9,7 @@ export declare class AdvancedLayoutManager extends AbstractLayoutManager {
|
|
|
9
9
|
private repositionCutoffs;
|
|
10
10
|
private repositionBackgrounds;
|
|
11
11
|
private resizeCanvas;
|
|
12
|
+
private dimensionsLayoutScheduled;
|
|
12
13
|
constructor(canvas: IInteractiveCanvas);
|
|
13
14
|
onCanvasDimensionsChange(): void;
|
|
14
15
|
onTextChange(): void;
|
|
@@ -63,6 +63,8 @@ export declare class Text extends AbstractCanvasObject<ExtendedFabricTextbox> im
|
|
|
63
63
|
private readonly text$;
|
|
64
64
|
private readonly textSelection$;
|
|
65
65
|
static readonly NEW_LINE: string;
|
|
66
|
+
private notifyCanvasTimeout;
|
|
67
|
+
private readonly NOTIFY_DELAY_MS;
|
|
66
68
|
constructor(canvas: IInteractiveCanvas, fabricObject: fabric.Textbox, persistedField?: CanvasObjectData);
|
|
67
69
|
/**
|
|
68
70
|
* @override
|