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