@bensitu/image-editor 1.5.1 → 1.5.2
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/README.md +71 -16
- package/dist/image-editor.cjs +241 -19
- package/dist/image-editor.cjs.map +2 -2
- package/dist/image-editor.esm.js +241 -19
- package/dist/image-editor.esm.js.map +2 -2
- package/dist/image-editor.esm.min.js +2 -2
- package/dist/image-editor.esm.min.js.map +3 -3
- package/dist/image-editor.esm.min.mjs +2 -2
- package/dist/image-editor.esm.min.mjs.map +3 -3
- package/dist/image-editor.esm.mjs +241 -19
- package/dist/image-editor.esm.mjs.map +2 -2
- package/dist/image-editor.js +241 -19
- package/dist/image-editor.js.map +2 -2
- package/dist/image-editor.min.js +2 -2
- package/dist/image-editor.min.js.map +3 -3
- package/image-editor.d.ts +2 -0
- package/package.json +1 -1
- package/src/image-editor.js +258 -20
package/dist/image-editor.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @file image-editor.js
|
|
5
5
|
* @module image-editor
|
|
6
|
-
* @version 1.5.
|
|
6
|
+
* @version 1.5.2
|
|
7
7
|
* @author Ben Situ
|
|
8
8
|
* @license MIT
|
|
9
9
|
* @description Lightweight canvas-based image editor with masking/transform/export support.
|
|
@@ -76,6 +76,7 @@
|
|
|
76
76
|
imageLoadTimeoutMs: 3e4,
|
|
77
77
|
exportMultiplier: 1,
|
|
78
78
|
maxExportPixels: 5e7,
|
|
79
|
+
maxHistorySize: 50,
|
|
79
80
|
exportImageAreaByDefault: true,
|
|
80
81
|
defaultMaskWidth: 50,
|
|
81
82
|
defaultMaskHeight: 80,
|
|
@@ -104,6 +105,7 @@
|
|
|
104
105
|
...userCrop
|
|
105
106
|
}
|
|
106
107
|
};
|
|
108
|
+
this._normalizeOptions();
|
|
107
109
|
this._fabricLoaded = !!ensureFabric();
|
|
108
110
|
if (!this._fabricLoaded) {
|
|
109
111
|
this._reportError("fabric.js is not loaded. Please include fabric.js first. Initialization will be aborted.");
|
|
@@ -123,16 +125,18 @@
|
|
|
123
125
|
this._activeOperationToken = null;
|
|
124
126
|
this.elements = {};
|
|
125
127
|
this.isImageLoadedToCanvas = false;
|
|
126
|
-
this.maxHistorySize =
|
|
128
|
+
this.maxHistorySize = this.options.maxHistorySize;
|
|
127
129
|
this._handlersByElementKey = {};
|
|
128
130
|
this._elementCache = {};
|
|
129
131
|
this._elementOriginalPointerEvents = /* @__PURE__ */ new Map();
|
|
132
|
+
this._elementOriginalDisabledState = /* @__PURE__ */ new Map();
|
|
130
133
|
this._lastMask = null;
|
|
131
134
|
this._lastMaskInitialLeft = null;
|
|
132
135
|
this._lastMaskInitialTop = null;
|
|
133
136
|
this._lastMaskInitialWidth = null;
|
|
134
137
|
this._lastSnapshot = null;
|
|
135
138
|
this._cropMode = false;
|
|
139
|
+
this._isApplyingCrop = false;
|
|
136
140
|
this._cropRect = null;
|
|
137
141
|
this._cropHandlers = [];
|
|
138
142
|
this._cropPrevEvented = null;
|
|
@@ -229,6 +233,8 @@
|
|
|
229
233
|
this._activeOperationName = null;
|
|
230
234
|
this._activeOperationToken = null;
|
|
231
235
|
this._elementOriginalPointerEvents = /* @__PURE__ */ new Map();
|
|
236
|
+
this._elementOriginalDisabledState = /* @__PURE__ */ new Map();
|
|
237
|
+
this._isApplyingCrop = false;
|
|
232
238
|
this._containerOriginalOverflow = null;
|
|
233
239
|
this._lastContainerViewportSize = null;
|
|
234
240
|
this._canvasElementOriginalStyle = null;
|
|
@@ -348,6 +354,54 @@
|
|
|
348
354
|
`ElementIdMap.${deprecatedKey} is deprecated. Use ${canonicalKey} instead. This alias will be removed in v2.0.0.`
|
|
349
355
|
);
|
|
350
356
|
}
|
|
357
|
+
_normalizeFiniteNumber(value, fallback) {
|
|
358
|
+
const numericValue = Number(value);
|
|
359
|
+
return Number.isFinite(numericValue) ? numericValue : fallback;
|
|
360
|
+
}
|
|
361
|
+
_normalizePositiveNumber(value, fallback) {
|
|
362
|
+
const numericValue = this._normalizeFiniteNumber(value, fallback);
|
|
363
|
+
return numericValue > 0 ? numericValue : fallback;
|
|
364
|
+
}
|
|
365
|
+
_normalizeNonNegativeNumber(value, fallback) {
|
|
366
|
+
const numericValue = this._normalizeFiniteNumber(value, fallback);
|
|
367
|
+
return numericValue >= 0 ? numericValue : fallback;
|
|
368
|
+
}
|
|
369
|
+
_normalizePositiveInteger(value, fallback) {
|
|
370
|
+
const numericValue = this._normalizePositiveNumber(value, fallback);
|
|
371
|
+
return Math.max(1, Math.floor(numericValue));
|
|
372
|
+
}
|
|
373
|
+
_normalizeOptions() {
|
|
374
|
+
const options = this.options || {};
|
|
375
|
+
options.canvasWidth = this._normalizePositiveNumber(options.canvasWidth, 800);
|
|
376
|
+
options.canvasHeight = this._normalizePositiveNumber(options.canvasHeight, 600);
|
|
377
|
+
options.animationDuration = this._normalizeNonNegativeNumber(options.animationDuration, 300);
|
|
378
|
+
const minScale = this._normalizePositiveNumber(options.minScale, 0.1);
|
|
379
|
+
const maxScale = this._normalizePositiveNumber(options.maxScale, 5);
|
|
380
|
+
if (minScale > maxScale) {
|
|
381
|
+
options.minScale = 0.1;
|
|
382
|
+
options.maxScale = 5;
|
|
383
|
+
} else {
|
|
384
|
+
options.minScale = minScale;
|
|
385
|
+
options.maxScale = maxScale;
|
|
386
|
+
}
|
|
387
|
+
options.scaleStep = this._normalizePositiveNumber(options.scaleStep, 0.05);
|
|
388
|
+
options.rotationStep = this._normalizeFiniteNumber(options.rotationStep, 90);
|
|
389
|
+
options.downsampleMaxWidth = this._normalizePositiveNumber(options.downsampleMaxWidth, 4e3);
|
|
390
|
+
options.downsampleMaxHeight = this._normalizePositiveNumber(options.downsampleMaxHeight, 3e3);
|
|
391
|
+
options.downsampleQuality = options.downsampleQuality == null ? 0.92 : Math.max(0, Math.min(1, this._normalizeFiniteNumber(options.downsampleQuality, 0.92)));
|
|
392
|
+
options.imageLoadTimeoutMs = this._normalizePositiveNumber(options.imageLoadTimeoutMs, 3e4);
|
|
393
|
+
options.exportMultiplier = this._normalizePositiveNumber(options.exportMultiplier, 1);
|
|
394
|
+
options.maxExportPixels = this._normalizePositiveInteger(options.maxExportPixels, 5e7);
|
|
395
|
+
options.maxHistorySize = this._normalizePositiveInteger(options.maxHistorySize, 50);
|
|
396
|
+
options.defaultMaskWidth = this._normalizePositiveNumber(options.defaultMaskWidth, 50);
|
|
397
|
+
options.defaultMaskHeight = this._normalizePositiveNumber(options.defaultMaskHeight, 80);
|
|
398
|
+
options.maskLabelOffset = this._normalizeNonNegativeNumber(options.maskLabelOffset, 3);
|
|
399
|
+
if (options.crop) {
|
|
400
|
+
options.crop.minWidth = this._normalizePositiveNumber(options.crop.minWidth, 100);
|
|
401
|
+
options.crop.minHeight = this._normalizePositiveNumber(options.crop.minHeight, 100);
|
|
402
|
+
options.crop.padding = this._normalizeNonNegativeNumber(options.crop.padding, 10);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
351
405
|
_reportError(message, error = null) {
|
|
352
406
|
const handler = this.options && this.options.onError;
|
|
353
407
|
if (typeof handler !== "function") return;
|
|
@@ -364,10 +418,18 @@
|
|
|
364
418
|
} catch {
|
|
365
419
|
}
|
|
366
420
|
}
|
|
421
|
+
_emitSafeCallback(callback, message) {
|
|
422
|
+
if (typeof callback !== "function") return;
|
|
423
|
+
try {
|
|
424
|
+
callback();
|
|
425
|
+
} catch (error) {
|
|
426
|
+
this._reportWarning(message, error);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
367
429
|
_notifyImageLoaded() {
|
|
368
430
|
const optionsCallback = this.options && this.options.onImageLoaded;
|
|
369
431
|
const callback = typeof optionsCallback === "function" ? optionsCallback : this.onImageLoaded;
|
|
370
|
-
|
|
432
|
+
this._emitSafeCallback(callback, "onImageLoaded callback failed");
|
|
371
433
|
}
|
|
372
434
|
/**
|
|
373
435
|
* Initializes the Fabric canvas, viewport elements, and selection event handlers.
|
|
@@ -606,7 +668,6 @@
|
|
|
606
668
|
_loadImageFile(file) {
|
|
607
669
|
if (!this._isSupportedImageFile(file)) {
|
|
608
670
|
const error = new Error("Selected file is not a supported image");
|
|
609
|
-
this._reportError("Selected file is not a supported image", error);
|
|
610
671
|
return Promise.reject(error);
|
|
611
672
|
}
|
|
612
673
|
return new Promise((resolve, reject) => {
|
|
@@ -669,6 +730,7 @@
|
|
|
669
730
|
const isNestedOperation = this._isOwnInternalOperation(options);
|
|
670
731
|
const operationToken = isNestedOperation ? this._getInternalOperationToken(options) : this._beginBusyOperation("loadImage");
|
|
671
732
|
let transaction = null;
|
|
733
|
+
let shouldNotifyImageLoaded;
|
|
672
734
|
try {
|
|
673
735
|
this._isLoading = true;
|
|
674
736
|
this._updateUI();
|
|
@@ -757,7 +819,7 @@
|
|
|
757
819
|
this._updateUI();
|
|
758
820
|
this.canvas.renderAll();
|
|
759
821
|
this._lastSnapshot = this._captureCanvasStateOrThrow("loadImage");
|
|
760
|
-
|
|
822
|
+
shouldNotifyImageLoaded = true;
|
|
761
823
|
} catch (error) {
|
|
762
824
|
await this._rollbackLoadImageTransaction(
|
|
763
825
|
transaction,
|
|
@@ -769,6 +831,9 @@
|
|
|
769
831
|
if (!isNestedOperation) this._endBusyOperation(operationToken);
|
|
770
832
|
if (!this._disposed && this.canvas) this._updateUI();
|
|
771
833
|
}
|
|
834
|
+
if (shouldNotifyImageLoaded && !this._disposed && this.canvas) {
|
|
835
|
+
this._notifyImageLoaded();
|
|
836
|
+
}
|
|
772
837
|
}
|
|
773
838
|
/**
|
|
774
839
|
* Checks whether there is a loaded image on the current canvas.
|
|
@@ -785,7 +850,7 @@
|
|
|
785
850
|
* @public
|
|
786
851
|
*/
|
|
787
852
|
isBusy() {
|
|
788
|
-
return !!(this.isAnimating || this._cropMode || this._isLoading || this._activeOperationToken || this.animationQueue && this.animationQueue.isBusy());
|
|
853
|
+
return !!(this.isAnimating || this._cropMode || this._isApplyingCrop || this._isLoading || this._activeOperationToken || this.animationQueue && this.animationQueue.isBusy());
|
|
789
854
|
}
|
|
790
855
|
/**
|
|
791
856
|
* Creates an HTMLImageElement from a given data URL.
|
|
@@ -1582,7 +1647,23 @@
|
|
|
1582
1647
|
_getJpegBackgroundColor() {
|
|
1583
1648
|
const backgroundColor = String(this.options.backgroundColor || "").trim();
|
|
1584
1649
|
if (!backgroundColor || this._isTransparentCssColor(backgroundColor)) return "#ffffff";
|
|
1585
|
-
return backgroundColor;
|
|
1650
|
+
return this._isValidCanvasFillStyle(backgroundColor) ? backgroundColor : "#ffffff";
|
|
1651
|
+
}
|
|
1652
|
+
_isValidCanvasFillStyle(color) {
|
|
1653
|
+
try {
|
|
1654
|
+
if (typeof document === "undefined" || !document.createElement) return false;
|
|
1655
|
+
const validationCanvas = document.createElement("canvas");
|
|
1656
|
+
const context = validationCanvas.getContext && validationCanvas.getContext("2d");
|
|
1657
|
+
if (!context) return false;
|
|
1658
|
+
context.fillStyle = "#010203";
|
|
1659
|
+
context.fillStyle = color;
|
|
1660
|
+
if (context.fillStyle !== "#010203") return true;
|
|
1661
|
+
context.fillStyle = "#040506";
|
|
1662
|
+
context.fillStyle = color;
|
|
1663
|
+
return context.fillStyle !== "#040506";
|
|
1664
|
+
} catch {
|
|
1665
|
+
return false;
|
|
1666
|
+
}
|
|
1586
1667
|
}
|
|
1587
1668
|
_isTransparentCssColor(color) {
|
|
1588
1669
|
const normalizedColor = String(color || "").trim().toLowerCase();
|
|
@@ -1992,10 +2073,12 @@
|
|
|
1992
2073
|
async _scaleImageImpl(factor, options = {}) {
|
|
1993
2074
|
if (!this.originalImage || this._disposed) return;
|
|
1994
2075
|
if (this.isAnimating) return;
|
|
2076
|
+
const numericFactor = Number(factor);
|
|
2077
|
+
if (!Number.isFinite(numericFactor)) return;
|
|
1995
2078
|
const saveHistory = options.saveHistory !== false;
|
|
1996
2079
|
let didStartAnimation = false;
|
|
1997
2080
|
try {
|
|
1998
|
-
factor = Math.max(this.options.minScale, Math.min(this.options.maxScale,
|
|
2081
|
+
factor = Math.max(this.options.minScale, Math.min(this.options.maxScale, numericFactor));
|
|
1999
2082
|
this.currentScale = factor;
|
|
2000
2083
|
this.isAnimating = true;
|
|
2001
2084
|
didStartAnimation = true;
|
|
@@ -2061,7 +2144,8 @@
|
|
|
2061
2144
|
async _rotateImageImpl(degrees, options = {}) {
|
|
2062
2145
|
if (!this.originalImage || this._disposed) return;
|
|
2063
2146
|
if (this.isAnimating) return;
|
|
2064
|
-
|
|
2147
|
+
const numericDegrees = Number(degrees);
|
|
2148
|
+
if (!Number.isFinite(numericDegrees)) return;
|
|
2065
2149
|
const saveHistory = options.saveHistory !== false;
|
|
2066
2150
|
const image = this.originalImage;
|
|
2067
2151
|
const previousOriginX = image.originX || "left";
|
|
@@ -2070,6 +2154,7 @@
|
|
|
2070
2154
|
let didStartAnimation = false;
|
|
2071
2155
|
let didCompleteRotation = false;
|
|
2072
2156
|
try {
|
|
2157
|
+
degrees = numericDegrees;
|
|
2073
2158
|
this.currentRotation = degrees;
|
|
2074
2159
|
this.isAnimating = true;
|
|
2075
2160
|
didStartAnimation = true;
|
|
@@ -2573,7 +2658,11 @@
|
|
|
2573
2658
|
maskConfig.top = top;
|
|
2574
2659
|
let mask;
|
|
2575
2660
|
if (typeof maskConfig.fabricGenerator === "function") {
|
|
2576
|
-
|
|
2661
|
+
try {
|
|
2662
|
+
mask = maskConfig.fabricGenerator(maskConfig, this.canvas, this.options);
|
|
2663
|
+
} catch (error) {
|
|
2664
|
+
return rejectInvalidMask("fabricGenerator failed", error);
|
|
2665
|
+
}
|
|
2577
2666
|
} else {
|
|
2578
2667
|
switch (shapeType) {
|
|
2579
2668
|
case "circle":
|
|
@@ -2627,6 +2716,17 @@
|
|
|
2627
2716
|
} catch (error) {
|
|
2628
2717
|
return rejectInvalidMask("invalid polygon points", error);
|
|
2629
2718
|
}
|
|
2719
|
+
const uniquePointKeys = new Set(polygonPoints.map((point) => `${point.x}:${point.y}`));
|
|
2720
|
+
if (uniquePointKeys.size !== polygonPoints.length) {
|
|
2721
|
+
return rejectInvalidMask("polygon points must not contain duplicates");
|
|
2722
|
+
}
|
|
2723
|
+
const doubleArea = polygonPoints.reduce((area, point, index) => {
|
|
2724
|
+
const nextPoint = polygonPoints[(index + 1) % polygonPoints.length];
|
|
2725
|
+
return area + point.x * nextPoint.y - nextPoint.x * point.y;
|
|
2726
|
+
}, 0);
|
|
2727
|
+
if (Math.abs(doubleArea) < 1e-6) {
|
|
2728
|
+
return rejectInvalidMask("polygon masks must have a non-zero area");
|
|
2729
|
+
}
|
|
2630
2730
|
mask = new fabric.Polygon(polygonPoints, {
|
|
2631
2731
|
left,
|
|
2632
2732
|
top,
|
|
@@ -2705,7 +2805,12 @@
|
|
|
2705
2805
|
this._updateUI();
|
|
2706
2806
|
this.canvas.renderAll();
|
|
2707
2807
|
this.saveState();
|
|
2708
|
-
if (typeof maskConfig.onCreate === "function")
|
|
2808
|
+
if (typeof maskConfig.onCreate === "function") {
|
|
2809
|
+
this._emitSafeCallback(
|
|
2810
|
+
() => maskConfig.onCreate(mask, this.canvas),
|
|
2811
|
+
"createMask onCreate callback failed"
|
|
2812
|
+
);
|
|
2813
|
+
}
|
|
2709
2814
|
return mask;
|
|
2710
2815
|
}
|
|
2711
2816
|
/**
|
|
@@ -2909,8 +3014,15 @@
|
|
|
2909
3014
|
this._removeLabelForMask(mask);
|
|
2910
3015
|
let textObject = null;
|
|
2911
3016
|
if (this.options.label && typeof this.options.label.create === "function") {
|
|
2912
|
-
|
|
2913
|
-
|
|
3017
|
+
let didLabelCreateThrow = false;
|
|
3018
|
+
try {
|
|
3019
|
+
textObject = this.options.label.create(mask, fabric);
|
|
3020
|
+
} catch (error) {
|
|
3021
|
+
didLabelCreateThrow = true;
|
|
3022
|
+
this._reportWarning("label.create() failed; using the default label", error);
|
|
3023
|
+
textObject = null;
|
|
3024
|
+
}
|
|
3025
|
+
if (!didLabelCreateThrow && (!textObject || typeof textObject.set !== "function")) {
|
|
2914
3026
|
this._reportWarning("label.create() returned an invalid Fabric object; using the default label");
|
|
2915
3027
|
textObject = null;
|
|
2916
3028
|
}
|
|
@@ -2931,7 +3043,12 @@
|
|
|
2931
3043
|
};
|
|
2932
3044
|
if (this.options.label) {
|
|
2933
3045
|
if (typeof this.options.label.getText === "function") {
|
|
2934
|
-
|
|
3046
|
+
try {
|
|
3047
|
+
labelText = this.options.label.getText(mask, this._getMaskCreationIndex(mask));
|
|
3048
|
+
} catch (error) {
|
|
3049
|
+
this._reportWarning("label.getText() failed; using the mask name", error);
|
|
3050
|
+
labelText = mask.maskName;
|
|
3051
|
+
}
|
|
2935
3052
|
}
|
|
2936
3053
|
if (this.options.label.textOptions) {
|
|
2937
3054
|
Object.assign(textOptions, this.options.label.textOptions);
|
|
@@ -3443,6 +3560,38 @@
|
|
|
3443
3560
|
height
|
|
3444
3561
|
};
|
|
3445
3562
|
}
|
|
3563
|
+
_getCropRectRawBounds(cropRect) {
|
|
3564
|
+
if (!cropRect) return { left: NaN, top: NaN, width: NaN, height: NaN };
|
|
3565
|
+
return {
|
|
3566
|
+
left: Number(cropRect.left),
|
|
3567
|
+
top: Number(cropRect.top),
|
|
3568
|
+
width: Number(cropRect.width) * Math.abs(Number(cropRect.scaleX)),
|
|
3569
|
+
height: Number(cropRect.height) * Math.abs(Number(cropRect.scaleY))
|
|
3570
|
+
};
|
|
3571
|
+
}
|
|
3572
|
+
_isValidCropRegion(cropBounds, imageBounds) {
|
|
3573
|
+
if (!cropBounds || !imageBounds) return false;
|
|
3574
|
+
const left = Number(cropBounds.left);
|
|
3575
|
+
const top = Number(cropBounds.top);
|
|
3576
|
+
const width = Number(cropBounds.width);
|
|
3577
|
+
const height = Number(cropBounds.height);
|
|
3578
|
+
const imageLeft = Number(imageBounds.left);
|
|
3579
|
+
const imageTop = Number(imageBounds.top);
|
|
3580
|
+
const imageWidth = Number(imageBounds.width);
|
|
3581
|
+
const imageHeight = Number(imageBounds.height);
|
|
3582
|
+
if (![left, top, width, height, imageLeft, imageTop, imageWidth, imageHeight].every(Number.isFinite)) return false;
|
|
3583
|
+
if (width <= 0 || height <= 0 || imageWidth <= 0 || imageHeight <= 0) return false;
|
|
3584
|
+
const right = left + width;
|
|
3585
|
+
const bottom = top + height;
|
|
3586
|
+
const imageRight = imageLeft + imageWidth;
|
|
3587
|
+
const imageBottom = imageTop + imageHeight;
|
|
3588
|
+
const overlapsImage = left < imageRight && right > imageLeft && top < imageBottom && bottom > imageTop;
|
|
3589
|
+
if (!overlapsImage) return false;
|
|
3590
|
+
const canvasWidth = this.canvas ? Number(this.canvas.getWidth()) : NaN;
|
|
3591
|
+
const canvasHeight = this.canvas ? Number(this.canvas.getHeight()) : NaN;
|
|
3592
|
+
if (!Number.isFinite(canvasWidth) || !Number.isFinite(canvasHeight) || canvasWidth <= 0 || canvasHeight <= 0) return false;
|
|
3593
|
+
return left < canvasWidth && right > 0 && top < canvasHeight && bottom > 0;
|
|
3594
|
+
}
|
|
3446
3595
|
/**
|
|
3447
3596
|
* Enters crop mode by creating a resizable crop rectangle above the base image.
|
|
3448
3597
|
*
|
|
@@ -3454,6 +3603,10 @@
|
|
|
3454
3603
|
*/
|
|
3455
3604
|
enterCropMode() {
|
|
3456
3605
|
if (!this.canvas || !this.originalImage || this._cropMode) return;
|
|
3606
|
+
if (this._isApplyingCrop) {
|
|
3607
|
+
this._reportWarning("enterCropMode ignored because a crop is already being applied");
|
|
3608
|
+
return;
|
|
3609
|
+
}
|
|
3457
3610
|
if (!this._canMutateNow("enterCropMode")) return;
|
|
3458
3611
|
if (!this.isImageLoaded()) return;
|
|
3459
3612
|
this._removeCropRect();
|
|
@@ -3578,6 +3731,10 @@
|
|
|
3578
3731
|
* @public
|
|
3579
3732
|
*/
|
|
3580
3733
|
cancelCrop() {
|
|
3734
|
+
if (this._isApplyingCrop) {
|
|
3735
|
+
this._reportWarning("cancelCrop ignored because a crop is already being applied");
|
|
3736
|
+
return;
|
|
3737
|
+
}
|
|
3581
3738
|
if (!this.canvas || !this._cropMode) return;
|
|
3582
3739
|
this._removeCropRect();
|
|
3583
3740
|
this._restoreCropObjectState();
|
|
@@ -3601,11 +3758,23 @@
|
|
|
3601
3758
|
*/
|
|
3602
3759
|
async applyCrop() {
|
|
3603
3760
|
if (!this.canvas || !this._cropMode || !this._cropRect) return;
|
|
3761
|
+
if (this._isApplyingCrop) {
|
|
3762
|
+
this._reportWarning("applyCrop ignored because a crop is already being applied");
|
|
3763
|
+
return;
|
|
3764
|
+
}
|
|
3604
3765
|
this._assertIdleForOperation("applyCrop");
|
|
3766
|
+
this._isApplyingCrop = true;
|
|
3605
3767
|
const operationToken = this._beginBusyOperation("applyCrop");
|
|
3606
3768
|
const internalOptions = this._withInternalOperationOptions(operationToken);
|
|
3607
3769
|
try {
|
|
3608
3770
|
this._cropRect.setCoords();
|
|
3771
|
+
this.originalImage.setCoords();
|
|
3772
|
+
const imageBounds = this.originalImage.getBoundingRect(true, true);
|
|
3773
|
+
const rawCropBounds = this._getCropRectRawBounds(this._cropRect);
|
|
3774
|
+
if (!this._isValidCropRegion(rawCropBounds, imageBounds)) {
|
|
3775
|
+
this._reportWarning("applyCrop: crop region is invalid");
|
|
3776
|
+
return;
|
|
3777
|
+
}
|
|
3609
3778
|
const rectBounds = this._getCropRectContentBounds(this._cropRect);
|
|
3610
3779
|
const cropRegion = this._getClampedCanvasRegion(rectBounds, { includePartialPixels: false });
|
|
3611
3780
|
const shouldPreserveMasks = !!(this.options.crop && this.options.crop.preserveMasksAfterCrop);
|
|
@@ -3618,7 +3787,13 @@
|
|
|
3618
3787
|
beforeJson = null;
|
|
3619
3788
|
}
|
|
3620
3789
|
if (!beforeJson) {
|
|
3621
|
-
this.
|
|
3790
|
+
this._removeCropRect();
|
|
3791
|
+
this._cropMode = false;
|
|
3792
|
+
this.canvas.selection = !!this._prevSelectionSetting;
|
|
3793
|
+
this._prevSelectionSetting = void 0;
|
|
3794
|
+
this.canvas.discardActiveObject();
|
|
3795
|
+
this._updateUI();
|
|
3796
|
+
this.canvas.renderAll();
|
|
3622
3797
|
return;
|
|
3623
3798
|
}
|
|
3624
3799
|
const preservedMasks = [];
|
|
@@ -3694,6 +3869,7 @@
|
|
|
3694
3869
|
this._updateUI();
|
|
3695
3870
|
this.canvas.renderAll();
|
|
3696
3871
|
} finally {
|
|
3872
|
+
this._isApplyingCrop = false;
|
|
3697
3873
|
this._endBusyOperation(operationToken);
|
|
3698
3874
|
}
|
|
3699
3875
|
}
|
|
@@ -3725,9 +3901,11 @@
|
|
|
3725
3901
|
const isInCropMode = !!this._cropMode;
|
|
3726
3902
|
const isBusy = this.isBusy();
|
|
3727
3903
|
if (isInCropMode) {
|
|
3904
|
+
const cropInteractionKeys = /* @__PURE__ */ new Set(["canvas", "canvasContainer", "imagePlaceholder", "imgPlaceholder"]);
|
|
3728
3905
|
for (const key of Object.keys(this.elements || {})) {
|
|
3729
3906
|
const element = this._getElement(key);
|
|
3730
3907
|
if (!element) continue;
|
|
3908
|
+
if (cropInteractionKeys.has(key)) continue;
|
|
3731
3909
|
if (key === "applyCropButton" || key === "cancelCropButton" || key === "applyCropBtn" || key === "cancelCropBtn") {
|
|
3732
3910
|
this._setDisabled(key, false);
|
|
3733
3911
|
} else {
|
|
@@ -3765,9 +3943,44 @@
|
|
|
3765
3943
|
* @param {boolean} disabled - If true, disables the element; otherwise enables.
|
|
3766
3944
|
* @private
|
|
3767
3945
|
*/
|
|
3946
|
+
_rememberElementDisabledState(key, element) {
|
|
3947
|
+
if (!element) return;
|
|
3948
|
+
if (!this._elementOriginalDisabledState) this._elementOriginalDisabledState = /* @__PURE__ */ new Map();
|
|
3949
|
+
if (this._elementOriginalDisabledState.has(key)) return;
|
|
3950
|
+
this._elementOriginalDisabledState.set(key, {
|
|
3951
|
+
element,
|
|
3952
|
+
hasDisabledProperty: "disabled" in element,
|
|
3953
|
+
disabled: "disabled" in element ? !!element.disabled : void 0,
|
|
3954
|
+
ariaDisabled: element.getAttribute ? element.getAttribute("aria-disabled") : null,
|
|
3955
|
+
pointerEvents: element.style ? element.style.pointerEvents || "" : ""
|
|
3956
|
+
});
|
|
3957
|
+
}
|
|
3958
|
+
_restoreElementDisabledStates() {
|
|
3959
|
+
if (!this._elementOriginalDisabledState) return;
|
|
3960
|
+
for (const state of this._elementOriginalDisabledState.values()) {
|
|
3961
|
+
const element = state && state.element;
|
|
3962
|
+
if (!element) continue;
|
|
3963
|
+
try {
|
|
3964
|
+
if (state.hasDisabledProperty && "disabled" in element) {
|
|
3965
|
+
element.disabled = !!state.disabled;
|
|
3966
|
+
}
|
|
3967
|
+
if (element.getAttribute && element.setAttribute && element.removeAttribute) {
|
|
3968
|
+
if (state.ariaDisabled === null) {
|
|
3969
|
+
element.removeAttribute("aria-disabled");
|
|
3970
|
+
} else {
|
|
3971
|
+
element.setAttribute("aria-disabled", state.ariaDisabled);
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
if (element.style) element.style.pointerEvents = state.pointerEvents || "";
|
|
3975
|
+
} catch (error) {
|
|
3976
|
+
void error;
|
|
3977
|
+
}
|
|
3978
|
+
}
|
|
3979
|
+
}
|
|
3768
3980
|
_setDisabled(key, disabled) {
|
|
3769
3981
|
const element = this._getElement(key);
|
|
3770
3982
|
if (!element) return;
|
|
3983
|
+
this._rememberElementDisabledState(key, element);
|
|
3771
3984
|
if ("disabled" in element) {
|
|
3772
3985
|
element.disabled = !!disabled;
|
|
3773
3986
|
return;
|
|
@@ -3794,7 +4007,6 @@
|
|
|
3794
4007
|
* @private
|
|
3795
4008
|
*/
|
|
3796
4009
|
_updatePlaceholderStatus() {
|
|
3797
|
-
if (!this.options.showPlaceholder) return;
|
|
3798
4010
|
this._setPlaceholderVisible(!this.originalImage);
|
|
3799
4011
|
}
|
|
3800
4012
|
/**
|
|
@@ -3804,10 +4016,11 @@
|
|
|
3804
4016
|
* @private
|
|
3805
4017
|
*/
|
|
3806
4018
|
_setPlaceholderVisible(show) {
|
|
3807
|
-
|
|
4019
|
+
const shouldShowPlaceholder = !!show && this.options.showPlaceholder !== false;
|
|
4020
|
+
if (this.placeholderElement) this._setElementVisible(this.placeholderElement, shouldShowPlaceholder);
|
|
3808
4021
|
const canvasVisibilityElement = this._getCanvasVisibilityElement();
|
|
3809
4022
|
if (canvasVisibilityElement && canvasVisibilityElement !== this.placeholderElement) {
|
|
3810
|
-
this._setElementVisible(canvasVisibilityElement, !
|
|
4023
|
+
this._setElementVisible(canvasVisibilityElement, !shouldShowPlaceholder);
|
|
3811
4024
|
}
|
|
3812
4025
|
}
|
|
3813
4026
|
_getCanvasVisibilityElement() {
|
|
@@ -3886,6 +4099,12 @@
|
|
|
3886
4099
|
void error;
|
|
3887
4100
|
}
|
|
3888
4101
|
if (this._cropRect) this._removeCropRect();
|
|
4102
|
+
this._isApplyingCrop = false;
|
|
4103
|
+
try {
|
|
4104
|
+
this._restoreElementDisabledStates();
|
|
4105
|
+
} catch (error) {
|
|
4106
|
+
void error;
|
|
4107
|
+
}
|
|
3889
4108
|
if (this.containerElement && this._containerOriginalOverflow) {
|
|
3890
4109
|
try {
|
|
3891
4110
|
this._restoreContainerOverflowState();
|
|
@@ -3933,6 +4152,7 @@
|
|
|
3933
4152
|
this._handlersByElementKey = {};
|
|
3934
4153
|
this._elementCache = {};
|
|
3935
4154
|
this._elementOriginalPointerEvents = /* @__PURE__ */ new Map();
|
|
4155
|
+
this._elementOriginalDisabledState = /* @__PURE__ */ new Map();
|
|
3936
4156
|
this._clearMaskPlacementMemory();
|
|
3937
4157
|
this.originalImage = null;
|
|
3938
4158
|
this.baseImageScale = 1;
|
|
@@ -3941,6 +4161,7 @@
|
|
|
3941
4161
|
this.isAnimating = false;
|
|
3942
4162
|
this._isLoading = false;
|
|
3943
4163
|
this._cropMode = false;
|
|
4164
|
+
this._isApplyingCrop = false;
|
|
3944
4165
|
this._cropRect = null;
|
|
3945
4166
|
this._cropHandlers = [];
|
|
3946
4167
|
this._cropPrevEvented = null;
|
|
@@ -4043,9 +4264,10 @@
|
|
|
4043
4264
|
* @param {number} [maxSize=50] - Maximum number of commands to keep in history.
|
|
4044
4265
|
*/
|
|
4045
4266
|
constructor(maxSize = 50) {
|
|
4267
|
+
const numericMaxSize = Number(maxSize);
|
|
4046
4268
|
this.history = [];
|
|
4047
4269
|
this.currentIndex = -1;
|
|
4048
|
-
this.maxSize =
|
|
4270
|
+
this.maxSize = Number.isFinite(numericMaxSize) && numericMaxSize > 0 ? Math.floor(numericMaxSize) : 50;
|
|
4049
4271
|
this.pending = Promise.resolve();
|
|
4050
4272
|
}
|
|
4051
4273
|
/**
|