@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.cjs
CHANGED
|
@@ -39,7 +39,7 @@ var import_fabric = __toESM(require("fabric"));
|
|
|
39
39
|
/**
|
|
40
40
|
* @file image-editor.js
|
|
41
41
|
* @module image-editor
|
|
42
|
-
* @version 1.5.
|
|
42
|
+
* @version 1.5.2
|
|
43
43
|
* @author Ben Situ
|
|
44
44
|
* @license MIT
|
|
45
45
|
* @description Lightweight canvas-based image editor with masking/transform/export support.
|
|
@@ -112,6 +112,7 @@ var ImageEditor = class {
|
|
|
112
112
|
imageLoadTimeoutMs: 3e4,
|
|
113
113
|
exportMultiplier: 1,
|
|
114
114
|
maxExportPixels: 5e7,
|
|
115
|
+
maxHistorySize: 50,
|
|
115
116
|
exportImageAreaByDefault: true,
|
|
116
117
|
defaultMaskWidth: 50,
|
|
117
118
|
defaultMaskHeight: 80,
|
|
@@ -140,6 +141,7 @@ var ImageEditor = class {
|
|
|
140
141
|
...userCrop
|
|
141
142
|
}
|
|
142
143
|
};
|
|
144
|
+
this._normalizeOptions();
|
|
143
145
|
this._fabricLoaded = !!ensureFabric();
|
|
144
146
|
if (!this._fabricLoaded) {
|
|
145
147
|
this._reportError("fabric.js is not loaded. Please include fabric.js first. Initialization will be aborted.");
|
|
@@ -159,16 +161,18 @@ var ImageEditor = class {
|
|
|
159
161
|
this._activeOperationToken = null;
|
|
160
162
|
this.elements = {};
|
|
161
163
|
this.isImageLoadedToCanvas = false;
|
|
162
|
-
this.maxHistorySize =
|
|
164
|
+
this.maxHistorySize = this.options.maxHistorySize;
|
|
163
165
|
this._handlersByElementKey = {};
|
|
164
166
|
this._elementCache = {};
|
|
165
167
|
this._elementOriginalPointerEvents = /* @__PURE__ */ new Map();
|
|
168
|
+
this._elementOriginalDisabledState = /* @__PURE__ */ new Map();
|
|
166
169
|
this._lastMask = null;
|
|
167
170
|
this._lastMaskInitialLeft = null;
|
|
168
171
|
this._lastMaskInitialTop = null;
|
|
169
172
|
this._lastMaskInitialWidth = null;
|
|
170
173
|
this._lastSnapshot = null;
|
|
171
174
|
this._cropMode = false;
|
|
175
|
+
this._isApplyingCrop = false;
|
|
172
176
|
this._cropRect = null;
|
|
173
177
|
this._cropHandlers = [];
|
|
174
178
|
this._cropPrevEvented = null;
|
|
@@ -265,6 +269,8 @@ var ImageEditor = class {
|
|
|
265
269
|
this._activeOperationName = null;
|
|
266
270
|
this._activeOperationToken = null;
|
|
267
271
|
this._elementOriginalPointerEvents = /* @__PURE__ */ new Map();
|
|
272
|
+
this._elementOriginalDisabledState = /* @__PURE__ */ new Map();
|
|
273
|
+
this._isApplyingCrop = false;
|
|
268
274
|
this._containerOriginalOverflow = null;
|
|
269
275
|
this._lastContainerViewportSize = null;
|
|
270
276
|
this._canvasElementOriginalStyle = null;
|
|
@@ -384,6 +390,54 @@ var ImageEditor = class {
|
|
|
384
390
|
`ElementIdMap.${deprecatedKey} is deprecated. Use ${canonicalKey} instead. This alias will be removed in v2.0.0.`
|
|
385
391
|
);
|
|
386
392
|
}
|
|
393
|
+
_normalizeFiniteNumber(value, fallback) {
|
|
394
|
+
const numericValue = Number(value);
|
|
395
|
+
return Number.isFinite(numericValue) ? numericValue : fallback;
|
|
396
|
+
}
|
|
397
|
+
_normalizePositiveNumber(value, fallback) {
|
|
398
|
+
const numericValue = this._normalizeFiniteNumber(value, fallback);
|
|
399
|
+
return numericValue > 0 ? numericValue : fallback;
|
|
400
|
+
}
|
|
401
|
+
_normalizeNonNegativeNumber(value, fallback) {
|
|
402
|
+
const numericValue = this._normalizeFiniteNumber(value, fallback);
|
|
403
|
+
return numericValue >= 0 ? numericValue : fallback;
|
|
404
|
+
}
|
|
405
|
+
_normalizePositiveInteger(value, fallback) {
|
|
406
|
+
const numericValue = this._normalizePositiveNumber(value, fallback);
|
|
407
|
+
return Math.max(1, Math.floor(numericValue));
|
|
408
|
+
}
|
|
409
|
+
_normalizeOptions() {
|
|
410
|
+
const options = this.options || {};
|
|
411
|
+
options.canvasWidth = this._normalizePositiveNumber(options.canvasWidth, 800);
|
|
412
|
+
options.canvasHeight = this._normalizePositiveNumber(options.canvasHeight, 600);
|
|
413
|
+
options.animationDuration = this._normalizeNonNegativeNumber(options.animationDuration, 300);
|
|
414
|
+
const minScale = this._normalizePositiveNumber(options.minScale, 0.1);
|
|
415
|
+
const maxScale = this._normalizePositiveNumber(options.maxScale, 5);
|
|
416
|
+
if (minScale > maxScale) {
|
|
417
|
+
options.minScale = 0.1;
|
|
418
|
+
options.maxScale = 5;
|
|
419
|
+
} else {
|
|
420
|
+
options.minScale = minScale;
|
|
421
|
+
options.maxScale = maxScale;
|
|
422
|
+
}
|
|
423
|
+
options.scaleStep = this._normalizePositiveNumber(options.scaleStep, 0.05);
|
|
424
|
+
options.rotationStep = this._normalizeFiniteNumber(options.rotationStep, 90);
|
|
425
|
+
options.downsampleMaxWidth = this._normalizePositiveNumber(options.downsampleMaxWidth, 4e3);
|
|
426
|
+
options.downsampleMaxHeight = this._normalizePositiveNumber(options.downsampleMaxHeight, 3e3);
|
|
427
|
+
options.downsampleQuality = options.downsampleQuality == null ? 0.92 : Math.max(0, Math.min(1, this._normalizeFiniteNumber(options.downsampleQuality, 0.92)));
|
|
428
|
+
options.imageLoadTimeoutMs = this._normalizePositiveNumber(options.imageLoadTimeoutMs, 3e4);
|
|
429
|
+
options.exportMultiplier = this._normalizePositiveNumber(options.exportMultiplier, 1);
|
|
430
|
+
options.maxExportPixels = this._normalizePositiveInteger(options.maxExportPixels, 5e7);
|
|
431
|
+
options.maxHistorySize = this._normalizePositiveInteger(options.maxHistorySize, 50);
|
|
432
|
+
options.defaultMaskWidth = this._normalizePositiveNumber(options.defaultMaskWidth, 50);
|
|
433
|
+
options.defaultMaskHeight = this._normalizePositiveNumber(options.defaultMaskHeight, 80);
|
|
434
|
+
options.maskLabelOffset = this._normalizeNonNegativeNumber(options.maskLabelOffset, 3);
|
|
435
|
+
if (options.crop) {
|
|
436
|
+
options.crop.minWidth = this._normalizePositiveNumber(options.crop.minWidth, 100);
|
|
437
|
+
options.crop.minHeight = this._normalizePositiveNumber(options.crop.minHeight, 100);
|
|
438
|
+
options.crop.padding = this._normalizeNonNegativeNumber(options.crop.padding, 10);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
387
441
|
_reportError(message, error = null) {
|
|
388
442
|
const handler = this.options && this.options.onError;
|
|
389
443
|
if (typeof handler !== "function") return;
|
|
@@ -400,10 +454,18 @@ var ImageEditor = class {
|
|
|
400
454
|
} catch {
|
|
401
455
|
}
|
|
402
456
|
}
|
|
457
|
+
_emitSafeCallback(callback, message) {
|
|
458
|
+
if (typeof callback !== "function") return;
|
|
459
|
+
try {
|
|
460
|
+
callback();
|
|
461
|
+
} catch (error) {
|
|
462
|
+
this._reportWarning(message, error);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
403
465
|
_notifyImageLoaded() {
|
|
404
466
|
const optionsCallback = this.options && this.options.onImageLoaded;
|
|
405
467
|
const callback = typeof optionsCallback === "function" ? optionsCallback : this.onImageLoaded;
|
|
406
|
-
|
|
468
|
+
this._emitSafeCallback(callback, "onImageLoaded callback failed");
|
|
407
469
|
}
|
|
408
470
|
/**
|
|
409
471
|
* Initializes the Fabric canvas, viewport elements, and selection event handlers.
|
|
@@ -642,7 +704,6 @@ var ImageEditor = class {
|
|
|
642
704
|
_loadImageFile(file) {
|
|
643
705
|
if (!this._isSupportedImageFile(file)) {
|
|
644
706
|
const error = new Error("Selected file is not a supported image");
|
|
645
|
-
this._reportError("Selected file is not a supported image", error);
|
|
646
707
|
return Promise.reject(error);
|
|
647
708
|
}
|
|
648
709
|
return new Promise((resolve, reject) => {
|
|
@@ -705,6 +766,7 @@ var ImageEditor = class {
|
|
|
705
766
|
const isNestedOperation = this._isOwnInternalOperation(options);
|
|
706
767
|
const operationToken = isNestedOperation ? this._getInternalOperationToken(options) : this._beginBusyOperation("loadImage");
|
|
707
768
|
let transaction = null;
|
|
769
|
+
let shouldNotifyImageLoaded;
|
|
708
770
|
try {
|
|
709
771
|
this._isLoading = true;
|
|
710
772
|
this._updateUI();
|
|
@@ -793,7 +855,7 @@ var ImageEditor = class {
|
|
|
793
855
|
this._updateUI();
|
|
794
856
|
this.canvas.renderAll();
|
|
795
857
|
this._lastSnapshot = this._captureCanvasStateOrThrow("loadImage");
|
|
796
|
-
|
|
858
|
+
shouldNotifyImageLoaded = true;
|
|
797
859
|
} catch (error) {
|
|
798
860
|
await this._rollbackLoadImageTransaction(
|
|
799
861
|
transaction,
|
|
@@ -805,6 +867,9 @@ var ImageEditor = class {
|
|
|
805
867
|
if (!isNestedOperation) this._endBusyOperation(operationToken);
|
|
806
868
|
if (!this._disposed && this.canvas) this._updateUI();
|
|
807
869
|
}
|
|
870
|
+
if (shouldNotifyImageLoaded && !this._disposed && this.canvas) {
|
|
871
|
+
this._notifyImageLoaded();
|
|
872
|
+
}
|
|
808
873
|
}
|
|
809
874
|
/**
|
|
810
875
|
* Checks whether there is a loaded image on the current canvas.
|
|
@@ -821,7 +886,7 @@ var ImageEditor = class {
|
|
|
821
886
|
* @public
|
|
822
887
|
*/
|
|
823
888
|
isBusy() {
|
|
824
|
-
return !!(this.isAnimating || this._cropMode || this._isLoading || this._activeOperationToken || this.animationQueue && this.animationQueue.isBusy());
|
|
889
|
+
return !!(this.isAnimating || this._cropMode || this._isApplyingCrop || this._isLoading || this._activeOperationToken || this.animationQueue && this.animationQueue.isBusy());
|
|
825
890
|
}
|
|
826
891
|
/**
|
|
827
892
|
* Creates an HTMLImageElement from a given data URL.
|
|
@@ -1618,7 +1683,23 @@ var ImageEditor = class {
|
|
|
1618
1683
|
_getJpegBackgroundColor() {
|
|
1619
1684
|
const backgroundColor = String(this.options.backgroundColor || "").trim();
|
|
1620
1685
|
if (!backgroundColor || this._isTransparentCssColor(backgroundColor)) return "#ffffff";
|
|
1621
|
-
return backgroundColor;
|
|
1686
|
+
return this._isValidCanvasFillStyle(backgroundColor) ? backgroundColor : "#ffffff";
|
|
1687
|
+
}
|
|
1688
|
+
_isValidCanvasFillStyle(color) {
|
|
1689
|
+
try {
|
|
1690
|
+
if (typeof document === "undefined" || !document.createElement) return false;
|
|
1691
|
+
const validationCanvas = document.createElement("canvas");
|
|
1692
|
+
const context = validationCanvas.getContext && validationCanvas.getContext("2d");
|
|
1693
|
+
if (!context) return false;
|
|
1694
|
+
context.fillStyle = "#010203";
|
|
1695
|
+
context.fillStyle = color;
|
|
1696
|
+
if (context.fillStyle !== "#010203") return true;
|
|
1697
|
+
context.fillStyle = "#040506";
|
|
1698
|
+
context.fillStyle = color;
|
|
1699
|
+
return context.fillStyle !== "#040506";
|
|
1700
|
+
} catch {
|
|
1701
|
+
return false;
|
|
1702
|
+
}
|
|
1622
1703
|
}
|
|
1623
1704
|
_isTransparentCssColor(color) {
|
|
1624
1705
|
const normalizedColor = String(color || "").trim().toLowerCase();
|
|
@@ -2028,10 +2109,12 @@ var ImageEditor = class {
|
|
|
2028
2109
|
async _scaleImageImpl(factor, options = {}) {
|
|
2029
2110
|
if (!this.originalImage || this._disposed) return;
|
|
2030
2111
|
if (this.isAnimating) return;
|
|
2112
|
+
const numericFactor = Number(factor);
|
|
2113
|
+
if (!Number.isFinite(numericFactor)) return;
|
|
2031
2114
|
const saveHistory = options.saveHistory !== false;
|
|
2032
2115
|
let didStartAnimation = false;
|
|
2033
2116
|
try {
|
|
2034
|
-
factor = Math.max(this.options.minScale, Math.min(this.options.maxScale,
|
|
2117
|
+
factor = Math.max(this.options.minScale, Math.min(this.options.maxScale, numericFactor));
|
|
2035
2118
|
this.currentScale = factor;
|
|
2036
2119
|
this.isAnimating = true;
|
|
2037
2120
|
didStartAnimation = true;
|
|
@@ -2097,7 +2180,8 @@ var ImageEditor = class {
|
|
|
2097
2180
|
async _rotateImageImpl(degrees, options = {}) {
|
|
2098
2181
|
if (!this.originalImage || this._disposed) return;
|
|
2099
2182
|
if (this.isAnimating) return;
|
|
2100
|
-
|
|
2183
|
+
const numericDegrees = Number(degrees);
|
|
2184
|
+
if (!Number.isFinite(numericDegrees)) return;
|
|
2101
2185
|
const saveHistory = options.saveHistory !== false;
|
|
2102
2186
|
const image = this.originalImage;
|
|
2103
2187
|
const previousOriginX = image.originX || "left";
|
|
@@ -2106,6 +2190,7 @@ var ImageEditor = class {
|
|
|
2106
2190
|
let didStartAnimation = false;
|
|
2107
2191
|
let didCompleteRotation = false;
|
|
2108
2192
|
try {
|
|
2193
|
+
degrees = numericDegrees;
|
|
2109
2194
|
this.currentRotation = degrees;
|
|
2110
2195
|
this.isAnimating = true;
|
|
2111
2196
|
didStartAnimation = true;
|
|
@@ -2609,7 +2694,11 @@ var ImageEditor = class {
|
|
|
2609
2694
|
maskConfig.top = top;
|
|
2610
2695
|
let mask;
|
|
2611
2696
|
if (typeof maskConfig.fabricGenerator === "function") {
|
|
2612
|
-
|
|
2697
|
+
try {
|
|
2698
|
+
mask = maskConfig.fabricGenerator(maskConfig, this.canvas, this.options);
|
|
2699
|
+
} catch (error) {
|
|
2700
|
+
return rejectInvalidMask("fabricGenerator failed", error);
|
|
2701
|
+
}
|
|
2613
2702
|
} else {
|
|
2614
2703
|
switch (shapeType) {
|
|
2615
2704
|
case "circle":
|
|
@@ -2663,6 +2752,17 @@ var ImageEditor = class {
|
|
|
2663
2752
|
} catch (error) {
|
|
2664
2753
|
return rejectInvalidMask("invalid polygon points", error);
|
|
2665
2754
|
}
|
|
2755
|
+
const uniquePointKeys = new Set(polygonPoints.map((point) => `${point.x}:${point.y}`));
|
|
2756
|
+
if (uniquePointKeys.size !== polygonPoints.length) {
|
|
2757
|
+
return rejectInvalidMask("polygon points must not contain duplicates");
|
|
2758
|
+
}
|
|
2759
|
+
const doubleArea = polygonPoints.reduce((area, point, index) => {
|
|
2760
|
+
const nextPoint = polygonPoints[(index + 1) % polygonPoints.length];
|
|
2761
|
+
return area + point.x * nextPoint.y - nextPoint.x * point.y;
|
|
2762
|
+
}, 0);
|
|
2763
|
+
if (Math.abs(doubleArea) < 1e-6) {
|
|
2764
|
+
return rejectInvalidMask("polygon masks must have a non-zero area");
|
|
2765
|
+
}
|
|
2666
2766
|
mask = new fabric.Polygon(polygonPoints, {
|
|
2667
2767
|
left,
|
|
2668
2768
|
top,
|
|
@@ -2741,7 +2841,12 @@ var ImageEditor = class {
|
|
|
2741
2841
|
this._updateUI();
|
|
2742
2842
|
this.canvas.renderAll();
|
|
2743
2843
|
this.saveState();
|
|
2744
|
-
if (typeof maskConfig.onCreate === "function")
|
|
2844
|
+
if (typeof maskConfig.onCreate === "function") {
|
|
2845
|
+
this._emitSafeCallback(
|
|
2846
|
+
() => maskConfig.onCreate(mask, this.canvas),
|
|
2847
|
+
"createMask onCreate callback failed"
|
|
2848
|
+
);
|
|
2849
|
+
}
|
|
2745
2850
|
return mask;
|
|
2746
2851
|
}
|
|
2747
2852
|
/**
|
|
@@ -2945,8 +3050,15 @@ var ImageEditor = class {
|
|
|
2945
3050
|
this._removeLabelForMask(mask);
|
|
2946
3051
|
let textObject = null;
|
|
2947
3052
|
if (this.options.label && typeof this.options.label.create === "function") {
|
|
2948
|
-
|
|
2949
|
-
|
|
3053
|
+
let didLabelCreateThrow = false;
|
|
3054
|
+
try {
|
|
3055
|
+
textObject = this.options.label.create(mask, fabric);
|
|
3056
|
+
} catch (error) {
|
|
3057
|
+
didLabelCreateThrow = true;
|
|
3058
|
+
this._reportWarning("label.create() failed; using the default label", error);
|
|
3059
|
+
textObject = null;
|
|
3060
|
+
}
|
|
3061
|
+
if (!didLabelCreateThrow && (!textObject || typeof textObject.set !== "function")) {
|
|
2950
3062
|
this._reportWarning("label.create() returned an invalid Fabric object; using the default label");
|
|
2951
3063
|
textObject = null;
|
|
2952
3064
|
}
|
|
@@ -2967,7 +3079,12 @@ var ImageEditor = class {
|
|
|
2967
3079
|
};
|
|
2968
3080
|
if (this.options.label) {
|
|
2969
3081
|
if (typeof this.options.label.getText === "function") {
|
|
2970
|
-
|
|
3082
|
+
try {
|
|
3083
|
+
labelText = this.options.label.getText(mask, this._getMaskCreationIndex(mask));
|
|
3084
|
+
} catch (error) {
|
|
3085
|
+
this._reportWarning("label.getText() failed; using the mask name", error);
|
|
3086
|
+
labelText = mask.maskName;
|
|
3087
|
+
}
|
|
2971
3088
|
}
|
|
2972
3089
|
if (this.options.label.textOptions) {
|
|
2973
3090
|
Object.assign(textOptions, this.options.label.textOptions);
|
|
@@ -3479,6 +3596,38 @@ var ImageEditor = class {
|
|
|
3479
3596
|
height
|
|
3480
3597
|
};
|
|
3481
3598
|
}
|
|
3599
|
+
_getCropRectRawBounds(cropRect) {
|
|
3600
|
+
if (!cropRect) return { left: NaN, top: NaN, width: NaN, height: NaN };
|
|
3601
|
+
return {
|
|
3602
|
+
left: Number(cropRect.left),
|
|
3603
|
+
top: Number(cropRect.top),
|
|
3604
|
+
width: Number(cropRect.width) * Math.abs(Number(cropRect.scaleX)),
|
|
3605
|
+
height: Number(cropRect.height) * Math.abs(Number(cropRect.scaleY))
|
|
3606
|
+
};
|
|
3607
|
+
}
|
|
3608
|
+
_isValidCropRegion(cropBounds, imageBounds) {
|
|
3609
|
+
if (!cropBounds || !imageBounds) return false;
|
|
3610
|
+
const left = Number(cropBounds.left);
|
|
3611
|
+
const top = Number(cropBounds.top);
|
|
3612
|
+
const width = Number(cropBounds.width);
|
|
3613
|
+
const height = Number(cropBounds.height);
|
|
3614
|
+
const imageLeft = Number(imageBounds.left);
|
|
3615
|
+
const imageTop = Number(imageBounds.top);
|
|
3616
|
+
const imageWidth = Number(imageBounds.width);
|
|
3617
|
+
const imageHeight = Number(imageBounds.height);
|
|
3618
|
+
if (![left, top, width, height, imageLeft, imageTop, imageWidth, imageHeight].every(Number.isFinite)) return false;
|
|
3619
|
+
if (width <= 0 || height <= 0 || imageWidth <= 0 || imageHeight <= 0) return false;
|
|
3620
|
+
const right = left + width;
|
|
3621
|
+
const bottom = top + height;
|
|
3622
|
+
const imageRight = imageLeft + imageWidth;
|
|
3623
|
+
const imageBottom = imageTop + imageHeight;
|
|
3624
|
+
const overlapsImage = left < imageRight && right > imageLeft && top < imageBottom && bottom > imageTop;
|
|
3625
|
+
if (!overlapsImage) return false;
|
|
3626
|
+
const canvasWidth = this.canvas ? Number(this.canvas.getWidth()) : NaN;
|
|
3627
|
+
const canvasHeight = this.canvas ? Number(this.canvas.getHeight()) : NaN;
|
|
3628
|
+
if (!Number.isFinite(canvasWidth) || !Number.isFinite(canvasHeight) || canvasWidth <= 0 || canvasHeight <= 0) return false;
|
|
3629
|
+
return left < canvasWidth && right > 0 && top < canvasHeight && bottom > 0;
|
|
3630
|
+
}
|
|
3482
3631
|
/**
|
|
3483
3632
|
* Enters crop mode by creating a resizable crop rectangle above the base image.
|
|
3484
3633
|
*
|
|
@@ -3490,6 +3639,10 @@ var ImageEditor = class {
|
|
|
3490
3639
|
*/
|
|
3491
3640
|
enterCropMode() {
|
|
3492
3641
|
if (!this.canvas || !this.originalImage || this._cropMode) return;
|
|
3642
|
+
if (this._isApplyingCrop) {
|
|
3643
|
+
this._reportWarning("enterCropMode ignored because a crop is already being applied");
|
|
3644
|
+
return;
|
|
3645
|
+
}
|
|
3493
3646
|
if (!this._canMutateNow("enterCropMode")) return;
|
|
3494
3647
|
if (!this.isImageLoaded()) return;
|
|
3495
3648
|
this._removeCropRect();
|
|
@@ -3614,6 +3767,10 @@ var ImageEditor = class {
|
|
|
3614
3767
|
* @public
|
|
3615
3768
|
*/
|
|
3616
3769
|
cancelCrop() {
|
|
3770
|
+
if (this._isApplyingCrop) {
|
|
3771
|
+
this._reportWarning("cancelCrop ignored because a crop is already being applied");
|
|
3772
|
+
return;
|
|
3773
|
+
}
|
|
3617
3774
|
if (!this.canvas || !this._cropMode) return;
|
|
3618
3775
|
this._removeCropRect();
|
|
3619
3776
|
this._restoreCropObjectState();
|
|
@@ -3637,11 +3794,23 @@ var ImageEditor = class {
|
|
|
3637
3794
|
*/
|
|
3638
3795
|
async applyCrop() {
|
|
3639
3796
|
if (!this.canvas || !this._cropMode || !this._cropRect) return;
|
|
3797
|
+
if (this._isApplyingCrop) {
|
|
3798
|
+
this._reportWarning("applyCrop ignored because a crop is already being applied");
|
|
3799
|
+
return;
|
|
3800
|
+
}
|
|
3640
3801
|
this._assertIdleForOperation("applyCrop");
|
|
3802
|
+
this._isApplyingCrop = true;
|
|
3641
3803
|
const operationToken = this._beginBusyOperation("applyCrop");
|
|
3642
3804
|
const internalOptions = this._withInternalOperationOptions(operationToken);
|
|
3643
3805
|
try {
|
|
3644
3806
|
this._cropRect.setCoords();
|
|
3807
|
+
this.originalImage.setCoords();
|
|
3808
|
+
const imageBounds = this.originalImage.getBoundingRect(true, true);
|
|
3809
|
+
const rawCropBounds = this._getCropRectRawBounds(this._cropRect);
|
|
3810
|
+
if (!this._isValidCropRegion(rawCropBounds, imageBounds)) {
|
|
3811
|
+
this._reportWarning("applyCrop: crop region is invalid");
|
|
3812
|
+
return;
|
|
3813
|
+
}
|
|
3645
3814
|
const rectBounds = this._getCropRectContentBounds(this._cropRect);
|
|
3646
3815
|
const cropRegion = this._getClampedCanvasRegion(rectBounds, { includePartialPixels: false });
|
|
3647
3816
|
const shouldPreserveMasks = !!(this.options.crop && this.options.crop.preserveMasksAfterCrop);
|
|
@@ -3654,7 +3823,13 @@ var ImageEditor = class {
|
|
|
3654
3823
|
beforeJson = null;
|
|
3655
3824
|
}
|
|
3656
3825
|
if (!beforeJson) {
|
|
3657
|
-
this.
|
|
3826
|
+
this._removeCropRect();
|
|
3827
|
+
this._cropMode = false;
|
|
3828
|
+
this.canvas.selection = !!this._prevSelectionSetting;
|
|
3829
|
+
this._prevSelectionSetting = void 0;
|
|
3830
|
+
this.canvas.discardActiveObject();
|
|
3831
|
+
this._updateUI();
|
|
3832
|
+
this.canvas.renderAll();
|
|
3658
3833
|
return;
|
|
3659
3834
|
}
|
|
3660
3835
|
const preservedMasks = [];
|
|
@@ -3730,6 +3905,7 @@ var ImageEditor = class {
|
|
|
3730
3905
|
this._updateUI();
|
|
3731
3906
|
this.canvas.renderAll();
|
|
3732
3907
|
} finally {
|
|
3908
|
+
this._isApplyingCrop = false;
|
|
3733
3909
|
this._endBusyOperation(operationToken);
|
|
3734
3910
|
}
|
|
3735
3911
|
}
|
|
@@ -3761,9 +3937,11 @@ var ImageEditor = class {
|
|
|
3761
3937
|
const isInCropMode = !!this._cropMode;
|
|
3762
3938
|
const isBusy = this.isBusy();
|
|
3763
3939
|
if (isInCropMode) {
|
|
3940
|
+
const cropInteractionKeys = /* @__PURE__ */ new Set(["canvas", "canvasContainer", "imagePlaceholder", "imgPlaceholder"]);
|
|
3764
3941
|
for (const key of Object.keys(this.elements || {})) {
|
|
3765
3942
|
const element = this._getElement(key);
|
|
3766
3943
|
if (!element) continue;
|
|
3944
|
+
if (cropInteractionKeys.has(key)) continue;
|
|
3767
3945
|
if (key === "applyCropButton" || key === "cancelCropButton" || key === "applyCropBtn" || key === "cancelCropBtn") {
|
|
3768
3946
|
this._setDisabled(key, false);
|
|
3769
3947
|
} else {
|
|
@@ -3801,9 +3979,44 @@ var ImageEditor = class {
|
|
|
3801
3979
|
* @param {boolean} disabled - If true, disables the element; otherwise enables.
|
|
3802
3980
|
* @private
|
|
3803
3981
|
*/
|
|
3982
|
+
_rememberElementDisabledState(key, element) {
|
|
3983
|
+
if (!element) return;
|
|
3984
|
+
if (!this._elementOriginalDisabledState) this._elementOriginalDisabledState = /* @__PURE__ */ new Map();
|
|
3985
|
+
if (this._elementOriginalDisabledState.has(key)) return;
|
|
3986
|
+
this._elementOriginalDisabledState.set(key, {
|
|
3987
|
+
element,
|
|
3988
|
+
hasDisabledProperty: "disabled" in element,
|
|
3989
|
+
disabled: "disabled" in element ? !!element.disabled : void 0,
|
|
3990
|
+
ariaDisabled: element.getAttribute ? element.getAttribute("aria-disabled") : null,
|
|
3991
|
+
pointerEvents: element.style ? element.style.pointerEvents || "" : ""
|
|
3992
|
+
});
|
|
3993
|
+
}
|
|
3994
|
+
_restoreElementDisabledStates() {
|
|
3995
|
+
if (!this._elementOriginalDisabledState) return;
|
|
3996
|
+
for (const state of this._elementOriginalDisabledState.values()) {
|
|
3997
|
+
const element = state && state.element;
|
|
3998
|
+
if (!element) continue;
|
|
3999
|
+
try {
|
|
4000
|
+
if (state.hasDisabledProperty && "disabled" in element) {
|
|
4001
|
+
element.disabled = !!state.disabled;
|
|
4002
|
+
}
|
|
4003
|
+
if (element.getAttribute && element.setAttribute && element.removeAttribute) {
|
|
4004
|
+
if (state.ariaDisabled === null) {
|
|
4005
|
+
element.removeAttribute("aria-disabled");
|
|
4006
|
+
} else {
|
|
4007
|
+
element.setAttribute("aria-disabled", state.ariaDisabled);
|
|
4008
|
+
}
|
|
4009
|
+
}
|
|
4010
|
+
if (element.style) element.style.pointerEvents = state.pointerEvents || "";
|
|
4011
|
+
} catch (error) {
|
|
4012
|
+
void error;
|
|
4013
|
+
}
|
|
4014
|
+
}
|
|
4015
|
+
}
|
|
3804
4016
|
_setDisabled(key, disabled) {
|
|
3805
4017
|
const element = this._getElement(key);
|
|
3806
4018
|
if (!element) return;
|
|
4019
|
+
this._rememberElementDisabledState(key, element);
|
|
3807
4020
|
if ("disabled" in element) {
|
|
3808
4021
|
element.disabled = !!disabled;
|
|
3809
4022
|
return;
|
|
@@ -3830,7 +4043,6 @@ var ImageEditor = class {
|
|
|
3830
4043
|
* @private
|
|
3831
4044
|
*/
|
|
3832
4045
|
_updatePlaceholderStatus() {
|
|
3833
|
-
if (!this.options.showPlaceholder) return;
|
|
3834
4046
|
this._setPlaceholderVisible(!this.originalImage);
|
|
3835
4047
|
}
|
|
3836
4048
|
/**
|
|
@@ -3840,10 +4052,11 @@ var ImageEditor = class {
|
|
|
3840
4052
|
* @private
|
|
3841
4053
|
*/
|
|
3842
4054
|
_setPlaceholderVisible(show) {
|
|
3843
|
-
|
|
4055
|
+
const shouldShowPlaceholder = !!show && this.options.showPlaceholder !== false;
|
|
4056
|
+
if (this.placeholderElement) this._setElementVisible(this.placeholderElement, shouldShowPlaceholder);
|
|
3844
4057
|
const canvasVisibilityElement = this._getCanvasVisibilityElement();
|
|
3845
4058
|
if (canvasVisibilityElement && canvasVisibilityElement !== this.placeholderElement) {
|
|
3846
|
-
this._setElementVisible(canvasVisibilityElement, !
|
|
4059
|
+
this._setElementVisible(canvasVisibilityElement, !shouldShowPlaceholder);
|
|
3847
4060
|
}
|
|
3848
4061
|
}
|
|
3849
4062
|
_getCanvasVisibilityElement() {
|
|
@@ -3922,6 +4135,12 @@ var ImageEditor = class {
|
|
|
3922
4135
|
void error;
|
|
3923
4136
|
}
|
|
3924
4137
|
if (this._cropRect) this._removeCropRect();
|
|
4138
|
+
this._isApplyingCrop = false;
|
|
4139
|
+
try {
|
|
4140
|
+
this._restoreElementDisabledStates();
|
|
4141
|
+
} catch (error) {
|
|
4142
|
+
void error;
|
|
4143
|
+
}
|
|
3925
4144
|
if (this.containerElement && this._containerOriginalOverflow) {
|
|
3926
4145
|
try {
|
|
3927
4146
|
this._restoreContainerOverflowState();
|
|
@@ -3969,6 +4188,7 @@ var ImageEditor = class {
|
|
|
3969
4188
|
this._handlersByElementKey = {};
|
|
3970
4189
|
this._elementCache = {};
|
|
3971
4190
|
this._elementOriginalPointerEvents = /* @__PURE__ */ new Map();
|
|
4191
|
+
this._elementOriginalDisabledState = /* @__PURE__ */ new Map();
|
|
3972
4192
|
this._clearMaskPlacementMemory();
|
|
3973
4193
|
this.originalImage = null;
|
|
3974
4194
|
this.baseImageScale = 1;
|
|
@@ -3977,6 +4197,7 @@ var ImageEditor = class {
|
|
|
3977
4197
|
this.isAnimating = false;
|
|
3978
4198
|
this._isLoading = false;
|
|
3979
4199
|
this._cropMode = false;
|
|
4200
|
+
this._isApplyingCrop = false;
|
|
3980
4201
|
this._cropRect = null;
|
|
3981
4202
|
this._cropHandlers = [];
|
|
3982
4203
|
this._cropPrevEvented = null;
|
|
@@ -4079,9 +4300,10 @@ var HistoryManager = class {
|
|
|
4079
4300
|
* @param {number} [maxSize=50] - Maximum number of commands to keep in history.
|
|
4080
4301
|
*/
|
|
4081
4302
|
constructor(maxSize = 50) {
|
|
4303
|
+
const numericMaxSize = Number(maxSize);
|
|
4082
4304
|
this.history = [];
|
|
4083
4305
|
this.currentIndex = -1;
|
|
4084
|
-
this.maxSize =
|
|
4306
|
+
this.maxSize = Number.isFinite(numericMaxSize) && numericMaxSize > 0 ? Math.floor(numericMaxSize) : 50;
|
|
4085
4307
|
this.pending = Promise.resolve();
|
|
4086
4308
|
}
|
|
4087
4309
|
/**
|