@bensitu/image-editor 1.3.0 → 1.3.1

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.
@@ -3,19 +3,16 @@
3
3
  /**
4
4
  * @file image-editor.js
5
5
  * @module image-editor
6
- * @version 1.3.0
6
+ * @version 1.3.1
7
7
  * @author Ben Situ
8
8
  * @license MIT
9
9
  * @description Lightweight canvas-based image editor with masking/transform/export support.
10
10
  */
11
11
  var fabric = null;
12
12
  function getGlobalScope() {
13
- if (typeof globalThis !== "undefined")
14
- return globalThis;
15
- if (typeof self !== "undefined")
16
- return self;
17
- if (typeof window !== "undefined")
18
- return window;
13
+ if (typeof globalThis !== "undefined") return globalThis;
14
+ if (typeof self !== "undefined") return self;
15
+ if (typeof window !== "undefined") return window;
19
16
  return null;
20
17
  }
21
18
  function getGlobalFabric() {
@@ -27,8 +24,7 @@
27
24
  return fabric;
28
25
  }
29
26
  function ensureFabric() {
30
- if (!fabric)
31
- setFabric();
27
+ if (!fabric) setFabric();
32
28
  return fabric;
33
29
  }
34
30
  var ImageEditor = class {
@@ -196,8 +192,7 @@
196
192
  * });
197
193
  */
198
194
  init(idMap = {}) {
199
- if (!this._fabricLoaded)
200
- return;
195
+ if (!this._fabricLoaded) return;
201
196
  const defaults = {
202
197
  canvas: "fabricCanvas",
203
198
  canvasContainer: null,
@@ -238,8 +233,7 @@
238
233
  }
239
234
  _reportError(message, error = null) {
240
235
  const handler = this.options && this.options.onError;
241
- if (typeof handler !== "function")
242
- return;
236
+ if (typeof handler !== "function") return;
243
237
  try {
244
238
  handler(error, message);
245
239
  } catch {
@@ -247,8 +241,7 @@
247
241
  }
248
242
  _reportWarning(message, error = null) {
249
243
  const handler = this.options && this.options.onWarning;
250
- if (typeof handler !== "function")
251
- return;
244
+ if (typeof handler !== "function") return;
252
245
  try {
253
246
  handler(error, message);
254
247
  } catch {
@@ -262,8 +255,7 @@
262
255
  */
263
256
  _initCanvas() {
264
257
  const canvasElement = document.getElementById(this.elements.canvas);
265
- if (!canvasElement)
266
- throw new Error("Canvas is not found: " + this.elements.canvas);
258
+ if (!canvasElement) throw new Error("Canvas is not found: " + this.elements.canvas);
267
259
  this.canvasElement = canvasElement;
268
260
  if (this.elements.canvasContainer) {
269
261
  const containerElement = document.getElementById(this.elements.canvasContainer);
@@ -293,16 +285,13 @@
293
285
  this.canvas.on("selection:updated", (event) => this._handleSelectionChanged(event.selected));
294
286
  this.canvas.on("selection:cleared", () => this._handleSelectionChanged([]));
295
287
  this.canvas.on("object:moving", (event) => {
296
- if (event.target && event.target.maskId)
297
- this._syncMaskLabel(event.target);
288
+ if (event.target && event.target.maskId) this._syncMaskLabel(event.target);
298
289
  });
299
290
  this.canvas.on("object:scaling", (event) => {
300
- if (event.target && event.target.maskId)
301
- this._syncMaskLabel(event.target);
291
+ if (event.target && event.target.maskId) this._syncMaskLabel(event.target);
302
292
  });
303
293
  this.canvas.on("object:rotating", (event) => {
304
- if (event.target && event.target.maskId)
305
- this._syncMaskLabel(event.target);
294
+ if (event.target && event.target.maskId) this._syncMaskLabel(event.target);
306
295
  });
307
296
  this.canvas.on("object:modified", (event) => this._handleObjectModified(event.target));
308
297
  this.canvasElement.style.display = "block";
@@ -316,11 +305,9 @@
316
305
  */
317
306
  _handleObjectModified(target) {
318
307
  const masks = this._getModifiedMasks(target);
319
- if (!masks.length)
320
- return;
308
+ if (!masks.length) return;
321
309
  masks.forEach((mask) => {
322
- if (typeof mask.setCoords === "function")
323
- mask.setCoords();
310
+ if (typeof mask.setCoords === "function") mask.setCoords();
324
311
  this._syncMaskLabel(mask);
325
312
  });
326
313
  this._expandCanvasToFitObjects(masks);
@@ -334,10 +321,8 @@
334
321
  * @private
335
322
  */
336
323
  _getModifiedMasks(target) {
337
- if (!target)
338
- return [];
339
- if (target.maskId)
340
- return [target];
324
+ if (!target) return [];
325
+ if (target.maskId) return [target];
341
326
  const objects = typeof target.getObjects === "function" ? target.getObjects() : [];
342
327
  return Array.isArray(objects) ? objects.filter((object) => object && object.maskId) : [];
343
328
  }
@@ -350,8 +335,7 @@
350
335
  * @private
351
336
  */
352
337
  _syncContainerOverflow(options = {}) {
353
- if (!this.containerElement || !this.containerElement.style)
354
- return;
338
+ if (!this.containerElement || !this.containerElement.style) return;
355
339
  if (this._containerOriginalOverflow === void 0) {
356
340
  this._containerOriginalOverflow = this.containerElement.style.overflow || "";
357
341
  }
@@ -379,14 +363,12 @@
379
363
  _bindEvents() {
380
364
  this._bindIfExists("uploadArea", "click", () => {
381
365
  const uploadAreaElement = document.getElementById(this.elements.uploadArea);
382
- if (this._isElementDisabled(uploadAreaElement))
383
- return;
366
+ if (this._isElementDisabled(uploadAreaElement)) return;
384
367
  document.getElementById(this.elements.imageInput)?.click();
385
368
  });
386
369
  this._bindIfExists("imageInput", "change", (event) => {
387
370
  const file = event.target.files && event.target.files[0];
388
- if (file)
389
- this._loadImageFile(file);
371
+ if (file) this._loadImageFile(file);
390
372
  });
391
373
  this._bindIfExists("zoomInBtn", "click", () => this.scaleImage(this.currentScale + this.options.scaleStep));
392
374
  this._bindIfExists("zoomOutBtn", "click", () => this.scaleImage(this.currentScale - this.options.scaleStep));
@@ -405,8 +387,7 @@
405
387
  let step = this.options.rotationStep;
406
388
  if (rotationInputElement) {
407
389
  const parsedStep = parseFloat(rotationInputElement.value);
408
- if (!isNaN(parsedStep))
409
- step = parsedStep;
390
+ if (!isNaN(parsedStep)) step = parsedStep;
410
391
  }
411
392
  this.rotateImage(this.currentRotation - step);
412
393
  });
@@ -415,8 +396,7 @@
415
396
  let step = this.options.rotationStep;
416
397
  if (rotationInputElement) {
417
398
  const parsedStep = parseFloat(rotationInputElement.value);
418
- if (!isNaN(parsedStep))
419
- step = parsedStep;
399
+ if (!isNaN(parsedStep)) step = parsedStep;
420
400
  }
421
401
  this.rotateImage(this.currentRotation + step);
422
402
  });
@@ -439,8 +419,7 @@
439
419
  if (element) {
440
420
  element.addEventListener(eventName, handler);
441
421
  this._handlersByElementKey = this._handlersByElementKey || {};
442
- if (!this._handlersByElementKey[key])
443
- this._handlersByElementKey[key] = [];
422
+ if (!this._handlersByElementKey[key]) this._handlersByElementKey[key] = [];
444
423
  this._handlersByElementKey[key].push({ eventName, handler });
445
424
  }
446
425
  }
@@ -451,8 +430,7 @@
451
430
  * @private
452
431
  */
453
432
  _loadImageFile(file) {
454
- if (!file || !file.type.startsWith("image/"))
455
- return;
433
+ if (!file || !file.type.startsWith("image/")) return;
456
434
  const reader = new FileReader();
457
435
  reader.onload = (event) => this.loadImage(event.target.result);
458
436
  reader.onerror = (event) => {
@@ -472,8 +450,7 @@
472
450
  ["coverImageToCanvas", this.options.coverImageToCanvas],
473
451
  ["expandCanvasToImage", this.options.expandCanvasToImage]
474
452
  ].filter(([, isEnabled]) => !!isEnabled).map(([name]) => name);
475
- if (activeModes.length <= 1)
476
- return;
453
+ if (activeModes.length <= 1) return;
477
454
  this._reportWarning(
478
455
  `Only one image layout mode should be enabled. Active modes: ${activeModes.join(", ")}.`
479
456
  );
@@ -488,12 +465,9 @@
488
465
  * @public
489
466
  */
490
467
  async loadImage(imageBase64, options = {}) {
491
- if (!this._fabricLoaded)
492
- return;
493
- if (!this.canvas)
494
- return;
495
- if (!imageBase64 || typeof imageBase64 !== "string" || !imageBase64.startsWith("data:image/"))
496
- return;
468
+ if (!this._fabricLoaded) return;
469
+ if (!this.canvas) return;
470
+ if (!imageBase64 || typeof imageBase64 !== "string" || !imageBase64.startsWith("data:image/")) return;
497
471
  this._warnOnImageLayoutOptionConflict();
498
472
  this._setPlaceholderVisible(false);
499
473
  this._syncContainerOverflow({ preserveScroll: options.preserveScroll === true });
@@ -514,8 +488,7 @@
514
488
  return new Promise((resolve, reject) => {
515
489
  fabric.Image.fromURL(loadSource, (fabricImage) => {
516
490
  try {
517
- if (!fabricImage)
518
- throw new Error("Image could not be loaded");
491
+ if (!fabricImage) throw new Error("Image could not be loaded");
519
492
  this.canvas.discardActiveObject();
520
493
  this._hideAllMaskLabels();
521
494
  this.canvas.clear();
@@ -609,8 +582,7 @@
609
582
  const safeTimeoutMs = Number.isFinite(Number(timeoutMs)) && Number(timeoutMs) > 0 ? Number(timeoutMs) : 3e4;
610
583
  let timerId;
611
584
  const settle = (callback) => {
612
- if (isSettled)
613
- return;
585
+ if (isSettled) return;
614
586
  isSettled = true;
615
587
  clearTimeout(timerId);
616
588
  imageElement.onload = null;
@@ -622,6 +594,7 @@
622
594
  try {
623
595
  imageElement.src = "";
624
596
  } catch (error) {
597
+ void error;
625
598
  }
626
599
  }, safeTimeoutMs);
627
600
  imageElement.onload = () => settle(() => resolve(imageElement));
@@ -644,8 +617,7 @@
644
617
  offscreenCanvas.width = targetWidth;
645
618
  offscreenCanvas.height = targetHeight;
646
619
  const context = offscreenCanvas.getContext("2d");
647
- if (!context)
648
- throw new Error("2D canvas context is unavailable");
620
+ if (!context) throw new Error("2D canvas context is unavailable");
649
621
  context.drawImage(imageElement, 0, 0, imageElement.naturalWidth, imageElement.naturalHeight, 0, 0, targetWidth, targetHeight);
650
622
  return offscreenCanvas.toDataURL("image/jpeg", quality);
651
623
  }
@@ -662,8 +634,7 @@
662
634
  const integerHeight = Math.max(1, Math.round(Number(height) || 1));
663
635
  this.canvas.setWidth(integerWidth);
664
636
  this.canvas.setHeight(integerHeight);
665
- if (typeof this.canvas.calcOffset === "function")
666
- this.canvas.calcOffset();
637
+ if (typeof this.canvas.calcOffset === "function") this.canvas.calcOffset();
667
638
  if (this.canvasElement) {
668
639
  this.canvasElement.style.width = integerWidth + "px";
669
640
  this.canvasElement.style.height = integerHeight + "px";
@@ -673,8 +644,7 @@
673
644
  _ceilCanvasDimension(value) {
674
645
  const numericValue = Number(value) || 0;
675
646
  const roundedValue = Math.round(numericValue);
676
- if (Math.abs(numericValue - roundedValue) < 0.01)
677
- return roundedValue;
647
+ if (Math.abs(numericValue - roundedValue) < 0.01) return roundedValue;
678
648
  return Math.ceil(numericValue);
679
649
  }
680
650
  _getContainerViewportSize() {
@@ -684,19 +654,31 @@
684
654
  height: Math.max(1, Math.floor(this.options.canvasHeight || 1))
685
655
  };
686
656
  }
657
+ let width = Math.max(1, Math.floor(this.containerElement.clientWidth || this.options.canvasWidth || 1));
658
+ let height = Math.max(1, Math.floor(this.containerElement.clientHeight || this.options.canvasHeight || 1));
687
659
  if (this._hasFixedContainerScrollbars()) {
688
- return {
689
- width: Math.max(1, Math.floor(this.containerElement.clientWidth || this.options.canvasWidth || 1)),
690
- height: Math.max(1, Math.floor(this.containerElement.clientHeight || this.options.canvasHeight || 1))
691
- };
660
+ return { width, height };
661
+ }
662
+ const overflow = this._getContainerOverflowValues();
663
+ const canScrollX = overflow.x.some((value) => value === "auto" || value === "scroll");
664
+ const canScrollY = overflow.y.some((value) => value === "auto" || value === "scroll");
665
+ const hasHorizontalScrollbar = canScrollX && this.containerElement.scrollWidth > this.containerElement.clientWidth;
666
+ const hasVerticalScrollbar = canScrollY && this.containerElement.scrollHeight > this.containerElement.clientHeight;
667
+ if (hasHorizontalScrollbar || hasVerticalScrollbar) {
668
+ const scrollbar = this._getScrollbarSize();
669
+ if (hasVerticalScrollbar) width += scrollbar.width;
670
+ if (hasHorizontalScrollbar) height += scrollbar.height;
692
671
  }
693
- const width = Math.max(1, Math.floor(this.containerElement.clientWidth || this.options.canvasWidth || 1));
694
- const height = Math.max(1, Math.floor(this.containerElement.clientHeight || this.options.canvasHeight || 1));
695
672
  return { width, height };
696
673
  }
697
- _hasFixedContainerScrollbars() {
698
- if (!this.containerElement)
699
- return false;
674
+ /**
675
+ * Reads inline and computed overflow values for both scroll axes.
676
+ *
677
+ * @returns {{x:string[], y:string[]}} Overflow values grouped by axis.
678
+ * @private
679
+ */
680
+ _getContainerOverflowValues() {
681
+ if (!this.containerElement) return { x: [], y: [] };
700
682
  const inlineOverflow = this.containerElement.style.overflow;
701
683
  const inlineOverflowX = this.containerElement.style.overflowX;
702
684
  const inlineOverflowY = this.containerElement.style.overflowY;
@@ -709,7 +691,15 @@
709
691
  computedOverflowX = style.overflowX;
710
692
  computedOverflowY = style.overflowY;
711
693
  }
712
- return [inlineOverflow, inlineOverflowX, inlineOverflowY, computedOverflow, computedOverflowX, computedOverflowY].some((value) => value === "scroll");
694
+ return {
695
+ x: [inlineOverflow, inlineOverflowX, computedOverflow, computedOverflowX],
696
+ y: [inlineOverflow, inlineOverflowY, computedOverflow, computedOverflowY]
697
+ };
698
+ }
699
+ _hasFixedContainerScrollbars() {
700
+ if (!this.containerElement) return false;
701
+ const overflow = this._getContainerOverflowValues();
702
+ return [...overflow.x, ...overflow.y].some((value) => value === "scroll");
713
703
  }
714
704
  _getScrollbarSize() {
715
705
  if (this._scrollbarSizeCache) {
@@ -752,15 +742,14 @@
752
742
  const scrollbar = this._getScrollbarSize();
753
743
  let hasVertical = false;
754
744
  let hasHorizontal = false;
755
- let effectiveWidth = viewport.width;
756
- let effectiveHeight = viewport.height;
745
+ let effectiveWidth;
746
+ let effectiveHeight;
757
747
  for (let i = 0; i < 4; i += 1) {
758
748
  effectiveWidth = Math.max(1, viewport.width - (hasVertical ? scrollbar.width : 0));
759
749
  effectiveHeight = Math.max(1, viewport.height - (hasHorizontal ? scrollbar.height : 0));
760
750
  const nextHasVertical = contentHeight > effectiveHeight + 0.5;
761
751
  const nextHasHorizontal = contentWidth > effectiveWidth + 0.5;
762
- if (nextHasVertical === hasVertical && nextHasHorizontal === hasHorizontal)
763
- break;
752
+ if (nextHasVertical === hasVertical && nextHasHorizontal === hasHorizontal) break;
764
753
  hasVertical = nextHasVertical;
765
754
  hasHorizontal = nextHasHorizontal;
766
755
  }
@@ -797,8 +786,8 @@
797
786
  let scale = 1;
798
787
  let contentWidth = imageWidth;
799
788
  let contentHeight = imageHeight;
800
- let effectiveWidth = viewport.width;
801
- let effectiveHeight = viewport.height;
789
+ let effectiveWidth;
790
+ let effectiveHeight;
802
791
  for (let i = 0; i < 4; i += 1) {
803
792
  effectiveWidth = Math.max(1, viewport.width - (hasVertical ? scrollbar.width : 0));
804
793
  effectiveHeight = Math.max(1, viewport.height - (hasHorizontal ? scrollbar.height : 0));
@@ -807,8 +796,7 @@
807
796
  contentHeight = imageHeight * scale;
808
797
  const nextHasVertical = contentHeight > effectiveHeight + 0.5;
809
798
  const nextHasHorizontal = contentWidth > effectiveWidth + 0.5;
810
- if (nextHasVertical === hasVertical && nextHasHorizontal === hasHorizontal)
811
- break;
799
+ if (nextHasVertical === hasVertical && nextHasHorizontal === hasHorizontal) break;
812
800
  hasVertical = nextHasVertical;
813
801
  hasHorizontal = nextHasHorizontal;
814
802
  }
@@ -847,41 +835,48 @@
847
835
  stroke: mask && mask.originalStroke || "#ccc",
848
836
  strokeWidth: Number.isFinite(strokeWidth) ? strokeWidth : 1
849
837
  };
850
- if (Number.isFinite(opacity))
851
- style.opacity = opacity;
838
+ if (Number.isFinite(opacity)) style.opacity = opacity;
852
839
  return style;
853
840
  }
854
841
  _withNormalizedMaskStyles(callback) {
855
- if (!this.canvas)
856
- return callback();
842
+ if (!this.canvas) return callback();
857
843
  const masks = this.canvas.getObjects().filter((object) => object.maskId);
858
- const maskStyleBackups = masks.map((mask) => ({
859
- object: mask,
860
- stroke: mask.stroke,
861
- strokeWidth: mask.strokeWidth,
862
- opacity: mask.opacity
863
- }));
844
+ const maskStyleBackups = [];
864
845
  try {
865
846
  masks.forEach((mask) => {
866
- mask.set(this._getMaskNormalStyle(mask));
847
+ const normalStyle = this._getMaskNormalStyle(mask);
848
+ const stylePatch = {};
849
+ Object.keys(normalStyle).forEach((property) => {
850
+ if (mask[property] !== normalStyle[property]) {
851
+ stylePatch[property] = normalStyle[property];
852
+ }
853
+ });
854
+ const changedProperties = Object.keys(stylePatch);
855
+ if (!changedProperties.length) return;
856
+ const backup = { object: mask };
857
+ changedProperties.forEach((property) => {
858
+ backup[property] = mask[property];
859
+ });
860
+ maskStyleBackups.push(backup);
861
+ mask.set(stylePatch);
867
862
  });
868
863
  return callback();
869
864
  } finally {
870
865
  maskStyleBackups.forEach((backup) => {
871
866
  try {
872
- backup.object.set({
873
- stroke: backup.stroke,
874
- strokeWidth: backup.strokeWidth,
875
- opacity: backup.opacity
867
+ const restorePatch = {};
868
+ Object.keys(backup).forEach((property) => {
869
+ if (property !== "object") restorePatch[property] = backup[property];
876
870
  });
871
+ backup.object.set(restorePatch);
877
872
  } catch (error) {
873
+ void error;
878
874
  }
879
875
  });
880
876
  }
881
877
  }
882
878
  _restoreMaskControls(mask) {
883
- if (!mask)
884
- return;
879
+ if (!mask) return;
885
880
  const cornerSize = Number(mask.cornerSize);
886
881
  mask.set({
887
882
  selectable: mask.selectable !== false,
@@ -894,8 +889,7 @@
894
889
  transparentCorners: mask.transparentCorners === true,
895
890
  strokeUniform: mask.strokeUniform !== false
896
891
  });
897
- if (typeof mask.setCoords === "function")
898
- mask.setCoords();
892
+ if (typeof mask.setCoords === "function") mask.setCoords();
899
893
  }
900
894
  /**
901
895
  * Captures editor-owned runtime state that Fabric does not include in canvas JSON.
@@ -917,8 +911,7 @@
917
911
  };
918
912
  }
919
913
  _serializeCanvasState() {
920
- if (!this.canvas)
921
- return null;
914
+ if (!this.canvas) return null;
922
915
  return this._withNormalizedMaskStyles(() => {
923
916
  const jsonObject = this.canvas.toJSON(this._getStateProperties());
924
917
  if (Array.isArray(jsonObject.objects)) {
@@ -937,8 +930,7 @@
937
930
  */
938
931
  _normalizeQuality(quality) {
939
932
  const numericQuality = Number(quality);
940
- if (!Number.isFinite(numericQuality))
941
- return this.options.downsampleQuality ?? 0.92;
933
+ if (!Number.isFinite(numericQuality)) return this.options.downsampleQuality ?? 0.92;
942
934
  return Math.max(0, Math.min(1, numericQuality));
943
935
  }
944
936
  /**
@@ -1011,8 +1003,7 @@
1011
1003
  const safeTimeoutMs = Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 3e4;
1012
1004
  let timerId;
1013
1005
  const settle = (callback) => {
1014
- if (isSettled)
1015
- return;
1006
+ if (isSettled) return;
1016
1007
  isSettled = true;
1017
1008
  clearTimeout(timerId);
1018
1009
  imageElement.onload = null;
@@ -1024,6 +1015,7 @@
1024
1015
  try {
1025
1016
  imageElement.src = "";
1026
1017
  } catch (error) {
1018
+ void error;
1027
1019
  }
1028
1020
  }, safeTimeoutMs);
1029
1021
  imageElement.onload = () => {
@@ -1037,8 +1029,7 @@
1037
1029
  offscreenCanvas.width = scaledSourceWidth;
1038
1030
  offscreenCanvas.height = scaledSourceHeight;
1039
1031
  const context = offscreenCanvas.getContext("2d");
1040
- if (!context)
1041
- throw new Error("2D canvas context is unavailable");
1032
+ if (!context) throw new Error("2D canvas context is unavailable");
1042
1033
  context.drawImage(imageElement, scaledSourceX, scaledSourceY, scaledSourceWidth, scaledSourceHeight, 0, 0, scaledSourceWidth, scaledSourceHeight);
1043
1034
  settle(() => resolve(offscreenCanvas.toDataURL(`image/${format}`, quality)));
1044
1035
  } catch (error) {
@@ -1081,12 +1072,10 @@
1081
1072
  * @private
1082
1073
  */
1083
1074
  _getObjectTopLeftPoint(fabricObject) {
1084
- if (!fabricObject)
1085
- return { x: 0, y: 0 };
1075
+ if (!fabricObject) return { x: 0, y: 0 };
1086
1076
  fabricObject.setCoords();
1087
1077
  const coords = typeof fabricObject.getCoords === "function" ? fabricObject.getCoords() : null;
1088
- if (coords && coords.length)
1089
- return coords[0];
1078
+ if (coords && coords.length) return coords[0];
1090
1079
  const boundingRect = fabricObject.getBoundingRect(true, true);
1091
1080
  return { x: boundingRect.left, y: boundingRect.top };
1092
1081
  }
@@ -1100,8 +1089,7 @@
1100
1089
  * @private
1101
1090
  */
1102
1091
  _setObjectOriginKeepingPosition(fabricObject, originX, originY, refPoint) {
1103
- if (!fabricObject || !refPoint || !fabricObject.setPositionByOrigin)
1104
- return;
1092
+ if (!fabricObject || !refPoint || !fabricObject.setPositionByOrigin) return;
1105
1093
  fabricObject.set({ originX, originY });
1106
1094
  fabricObject.setPositionByOrigin(refPoint, originX, originY);
1107
1095
  fabricObject.setCoords();
@@ -1113,8 +1101,7 @@
1113
1101
  * @private
1114
1102
  */
1115
1103
  _alignObjectBoundingBoxToCanvasTopLeft(fabricObject) {
1116
- if (!fabricObject)
1117
- return;
1104
+ if (!fabricObject) return;
1118
1105
  fabricObject.setCoords();
1119
1106
  const boundingRect = fabricObject.getBoundingRect(true, true);
1120
1107
  const deltaX = boundingRect.left;
@@ -1129,8 +1116,7 @@
1129
1116
  * @private
1130
1117
  */
1131
1118
  _updateCanvasSizeToImageBounds() {
1132
- if (!this.originalImage)
1133
- return;
1119
+ if (!this.originalImage) return;
1134
1120
  this.originalImage.setCoords();
1135
1121
  const imageBounds = this.originalImage.getBoundingRect(true, true);
1136
1122
  const size = this._getScrollableCanvasSize(imageBounds.width, imageBounds.height);
@@ -1154,16 +1140,13 @@
1154
1140
  * @private
1155
1141
  */
1156
1142
  _expandCanvasToFitObjects(fabricObjects, padding = 10) {
1157
- if (!this.canvas || !Array.isArray(fabricObjects) || !fabricObjects.length || !this._shouldResizeCanvasToContentBounds())
1158
- return;
1143
+ if (!this.canvas || !Array.isArray(fabricObjects) || !fabricObjects.length || !this._shouldResizeCanvasToContentBounds()) return;
1159
1144
  try {
1160
1145
  let requiredWidth = this.canvas.getWidth();
1161
1146
  let requiredHeight = this.canvas.getHeight();
1162
1147
  fabricObjects.forEach((fabricObject) => {
1163
- if (!fabricObject)
1164
- return;
1165
- if (typeof fabricObject.setCoords === "function")
1166
- fabricObject.setCoords();
1148
+ if (!fabricObject) return;
1149
+ if (typeof fabricObject.setCoords === "function") fabricObject.setCoords();
1167
1150
  const boundingRect = fabricObject.getBoundingRect(true, true);
1168
1151
  requiredWidth = Math.max(requiredWidth, Math.ceil(boundingRect.left + boundingRect.width + padding));
1169
1152
  requiredHeight = Math.max(requiredHeight, Math.ceil(boundingRect.top + boundingRect.height + padding));
@@ -1208,10 +1191,8 @@
1208
1191
  * @private
1209
1192
  */
1210
1193
  _scaleImageImpl(factor, options = {}) {
1211
- if (!this.originalImage)
1212
- return Promise.resolve();
1213
- if (this.isAnimating)
1214
- return Promise.resolve();
1194
+ if (!this.originalImage) return Promise.resolve();
1195
+ if (this.isAnimating) return Promise.resolve();
1215
1196
  const saveHistory = options.saveHistory !== false;
1216
1197
  factor = Math.max(this.options.minScale, Math.min(this.options.maxScale, factor));
1217
1198
  this.currentScale = factor;
@@ -1242,14 +1223,12 @@
1242
1223
  }
1243
1224
  this._alignObjectBoundingBoxToCanvasTopLeft(this.originalImage);
1244
1225
  this.canvas.getObjects().forEach((object) => {
1245
- if (object.maskId)
1246
- this._syncMaskLabel(object);
1226
+ if (object.maskId) this._syncMaskLabel(object);
1247
1227
  });
1248
1228
  this.isAnimating = false;
1249
1229
  this._updateInputs();
1250
1230
  this._updateUI();
1251
- if (saveHistory)
1252
- this.saveState();
1231
+ if (saveHistory) this.saveState();
1253
1232
  }).catch(() => {
1254
1233
  this.isAnimating = false;
1255
1234
  this._updateUI();
@@ -1273,12 +1252,9 @@
1273
1252
  * @private
1274
1253
  */
1275
1254
  _rotateImageImpl(degrees, options = {}) {
1276
- if (!this.originalImage)
1277
- return Promise.resolve();
1278
- if (this.isAnimating)
1279
- return Promise.resolve();
1280
- if (isNaN(degrees))
1281
- return Promise.resolve();
1255
+ if (!this.originalImage) return Promise.resolve();
1256
+ if (this.isAnimating) return Promise.resolve();
1257
+ if (isNaN(degrees)) return Promise.resolve();
1282
1258
  const saveHistory = options.saveHistory !== false;
1283
1259
  this.currentRotation = degrees;
1284
1260
  this.isAnimating = true;
@@ -1302,14 +1278,12 @@
1302
1278
  const newTopLeft = this._getObjectTopLeftPoint(this.originalImage);
1303
1279
  this._setObjectOriginKeepingPosition(this.originalImage, "left", "top", newTopLeft);
1304
1280
  this.canvas.getObjects().forEach((object) => {
1305
- if (object.maskId)
1306
- this._syncMaskLabel(object);
1281
+ if (object.maskId) this._syncMaskLabel(object);
1307
1282
  });
1308
1283
  this.isAnimating = false;
1309
1284
  this._updateInputs();
1310
1285
  this._updateUI();
1311
- if (saveHistory)
1312
- this.saveState();
1286
+ if (saveHistory) this.saveState();
1313
1287
  }).catch(() => {
1314
1288
  this.isAnimating = false;
1315
1289
  this._updateUI();
@@ -1322,8 +1296,7 @@
1322
1296
  * @public
1323
1297
  */
1324
1298
  resetImageTransform() {
1325
- if (!this.originalImage)
1326
- return Promise.resolve();
1299
+ if (!this.originalImage) return Promise.resolve();
1327
1300
  return this.animationQueue.add(async () => {
1328
1301
  const before = this._lastSnapshot || this._serializeCanvasState();
1329
1302
  await this._scaleImageImpl(1, { saveHistory: false });
@@ -1351,8 +1324,7 @@
1351
1324
  * @public
1352
1325
  */
1353
1326
  loadFromState(serializedState) {
1354
- if (!serializedState || !this.canvas)
1355
- return Promise.resolve();
1327
+ if (!serializedState || !this.canvas) return Promise.resolve();
1356
1328
  return new Promise((resolve) => {
1357
1329
  try {
1358
1330
  const state = typeof serializedState === "string" ? JSON.parse(serializedState) : serializedState;
@@ -1428,14 +1400,12 @@
1428
1400
  * @public
1429
1401
  */
1430
1402
  saveState() {
1431
- if (!this.canvas)
1432
- return;
1403
+ if (!this.canvas) return;
1433
1404
  const activeObject = this.canvas.getActiveObject();
1434
1405
  try {
1435
1406
  const after = this._serializeCanvasState();
1436
1407
  const before = this._lastSnapshot || after;
1437
- if (after === before)
1438
- return;
1408
+ if (after === before) return;
1439
1409
  let executedOnce = false;
1440
1410
  const command = new Command(
1441
1411
  () => {
@@ -1470,12 +1440,9 @@
1470
1440
  * @private
1471
1441
  */
1472
1442
  _pushStateTransition(before, after) {
1473
- if (!before || !after)
1474
- return;
1475
- if (before === after)
1476
- return;
1477
- if (!this.historyManager)
1478
- this.historyManager = new HistoryManager(this.maxHistorySize || 50);
1443
+ if (!before || !after) return;
1444
+ if (before === after) return;
1445
+ if (!this.historyManager) this.historyManager = new HistoryManager(this.maxHistorySize || 50);
1479
1446
  const command = new Command(
1480
1447
  () => this.loadFromState(after),
1481
1448
  () => this.loadFromState(before)
@@ -1511,26 +1478,24 @@
1511
1478
  });
1512
1479
  }
1513
1480
  _rebindMaskEvents(mask) {
1514
- if (!mask)
1515
- return;
1481
+ if (!mask) return;
1516
1482
  if (mask.__imageEditorMaskHandlers) {
1517
1483
  try {
1518
1484
  mask.off("mouseover", mask.__imageEditorMaskHandlers.mouseover);
1519
1485
  mask.off("mouseout", mask.__imageEditorMaskHandlers.mouseout);
1520
1486
  } catch (error) {
1487
+ void error;
1521
1488
  }
1522
1489
  }
1523
1490
  const metadata = {};
1524
1491
  if (!Number.isFinite(Number(mask.originalAlpha))) {
1525
1492
  metadata.originalAlpha = Number.isFinite(Number(mask.opacity)) ? Number(mask.opacity) : 0.5;
1526
1493
  }
1527
- if (!mask.originalStroke)
1528
- metadata.originalStroke = mask.stroke || "#ccc";
1494
+ if (!mask.originalStroke) metadata.originalStroke = mask.stroke || "#ccc";
1529
1495
  if (!Number.isFinite(Number(mask.originalStrokeWidth))) {
1530
1496
  metadata.originalStrokeWidth = Number.isFinite(Number(mask.strokeWidth)) ? Number(mask.strokeWidth) : 1;
1531
1497
  }
1532
- if (Object.keys(metadata).length)
1533
- mask.set(metadata);
1498
+ if (Object.keys(metadata).length) mask.set(metadata);
1534
1499
  const normalStyle = {
1535
1500
  stroke: mask.originalStroke || "#ccc",
1536
1501
  strokeWidth: mask.originalStrokeWidth,
@@ -1543,13 +1508,11 @@
1543
1508
  };
1544
1509
  const mouseover = () => {
1545
1510
  mask.set(hoverStyle);
1546
- if (mask.canvas)
1547
- mask.canvas.requestRenderAll();
1511
+ if (mask.canvas) mask.canvas.requestRenderAll();
1548
1512
  };
1549
1513
  const mouseout = () => {
1550
1514
  mask.set(normalStyle);
1551
- if (mask.canvas)
1552
- mask.canvas.requestRenderAll();
1515
+ if (mask.canvas) mask.canvas.requestRenderAll();
1553
1516
  };
1554
1517
  mask.on("mouseover", mouseover);
1555
1518
  mask.on("mouseout", mouseout);
@@ -1584,8 +1547,7 @@
1584
1547
  * @public
1585
1548
  */
1586
1549
  createMask(config = {}) {
1587
- if (!this.canvas)
1588
- return null;
1550
+ if (!this.canvas) return null;
1589
1551
  const shapeType = config.shape || "rect";
1590
1552
  const maskConfig = {
1591
1553
  shape: shapeType,
@@ -1601,14 +1563,22 @@
1601
1563
  ...config
1602
1564
  };
1603
1565
  const firstOffset = 10;
1604
- let left = firstOffset;
1605
- let top = firstOffset;
1606
- const resolveValue = (value, fallback) => {
1566
+ let left;
1567
+ let top;
1568
+ const getCanvasBasis = (axis) => {
1569
+ const canvasWidth = this.canvas ? this.canvas.getWidth() : 0;
1570
+ const canvasHeight = this.canvas ? this.canvas.getHeight() : 0;
1571
+ if (axis === "height") return canvasHeight;
1572
+ if (axis === "min") return Math.min(canvasWidth, canvasHeight);
1573
+ return canvasWidth;
1574
+ };
1575
+ const resolveValue = (value, fallback, axis = "width") => {
1607
1576
  if (typeof value === "function")
1608
1577
  return value(this.canvas, this.options);
1609
1578
  if (typeof value === "string" && value.endsWith("%")) {
1610
- const percent = parseFloat(value) / 100;
1611
- return Math.floor((this.canvas ? this.canvas.getWidth() : 0) * percent);
1579
+ const percent = Number.parseFloat(value) / 100;
1580
+ if (!Number.isFinite(percent)) return fallback;
1581
+ return Math.floor(getCanvasBasis(axis) * percent);
1612
1582
  }
1613
1583
  return value != null ? value : fallback;
1614
1584
  };
@@ -1623,11 +1593,11 @@
1623
1593
  left = Math.round(previousMaskRight + maskConfig.gap);
1624
1594
  top = previousMask.top ?? firstOffset;
1625
1595
  } else {
1626
- left = resolveValue(maskConfig.left, firstOffset);
1627
- top = resolveValue(maskConfig.top, firstOffset);
1596
+ left = resolveValue(maskConfig.left, firstOffset, "width");
1597
+ top = resolveValue(maskConfig.top, firstOffset, "height");
1628
1598
  }
1629
- maskConfig.width = resolveValue(maskConfig.width, this.options.defaultMaskWidth);
1630
- maskConfig.height = resolveValue(maskConfig.height, this.options.defaultMaskHeight);
1599
+ maskConfig.width = resolveValue(maskConfig.width, this.options.defaultMaskWidth, "width");
1600
+ maskConfig.height = resolveValue(maskConfig.height, this.options.defaultMaskHeight, "height");
1631
1601
  maskConfig.left = left;
1632
1602
  maskConfig.top = top;
1633
1603
  let mask;
@@ -1639,7 +1609,7 @@
1639
1609
  mask = new fabric.Circle({
1640
1610
  left,
1641
1611
  top,
1642
- radius: resolveValue(maskConfig.radius, Math.min(maskConfig.width, maskConfig.height) / 2),
1612
+ radius: resolveValue(maskConfig.radius, Math.min(maskConfig.width, maskConfig.height) / 2, "min"),
1643
1613
  fill: maskConfig.color,
1644
1614
  opacity: maskConfig.alpha,
1645
1615
  angle: maskConfig.angle,
@@ -1650,8 +1620,8 @@
1650
1620
  mask = new fabric.Ellipse({
1651
1621
  left,
1652
1622
  top,
1653
- rx: resolveValue(maskConfig.rx, maskConfig.width / 2),
1654
- ry: resolveValue(maskConfig.ry, maskConfig.height / 2),
1623
+ rx: resolveValue(maskConfig.rx, maskConfig.width / 2, "width"),
1624
+ ry: resolveValue(maskConfig.ry, maskConfig.height / 2, "height"),
1655
1625
  fill: maskConfig.color,
1656
1626
  opacity: maskConfig.alpha,
1657
1627
  angle: maskConfig.angle,
@@ -1678,8 +1648,8 @@
1678
1648
  mask = new fabric.Rect({
1679
1649
  left,
1680
1650
  top,
1681
- width: resolveValue(maskConfig.width, this.options.defaultMaskWidth),
1682
- height: resolveValue(maskConfig.height, this.options.defaultMaskHeight),
1651
+ width: resolveValue(maskConfig.width, this.options.defaultMaskWidth, "width"),
1652
+ height: resolveValue(maskConfig.height, this.options.defaultMaskHeight, "height"),
1683
1653
  fill: maskConfig.color,
1684
1654
  opacity: maskConfig.alpha,
1685
1655
  angle: maskConfig.angle,
@@ -1704,8 +1674,7 @@
1704
1674
  opacity: hasStyle("opacity") ? styles.opacity : maskConfig.alpha,
1705
1675
  strokeUniform: "strokeUniform" in maskConfig ? maskConfig.strokeUniform : hasStyle("strokeUniform") ? styles.strokeUniform : true
1706
1676
  };
1707
- if (hasStyle("strokeDashArray"))
1708
- maskSettings.strokeDashArray = styles.strokeDashArray;
1677
+ if (hasStyle("strokeDashArray")) maskSettings.strokeDashArray = styles.strokeDashArray;
1709
1678
  mask.set(maskSettings);
1710
1679
  mask.setCoords();
1711
1680
  mask.set({
@@ -1717,7 +1686,7 @@
1717
1686
  this._expandCanvasToFitObject(mask);
1718
1687
  this._lastMaskInitialLeft = left;
1719
1688
  this._lastMaskInitialTop = top;
1720
- this._lastMaskInitialWidth = resolveValue(maskConfig.width, this.options.defaultMaskWidth);
1689
+ this._lastMaskInitialWidth = resolveValue(maskConfig.width, this.options.defaultMaskWidth, "width");
1721
1690
  const maskId = ++this.maskCounter;
1722
1691
  mask.set({
1723
1692
  maskId,
@@ -1726,15 +1695,13 @@
1726
1695
  this._lastMask = mask;
1727
1696
  this.canvas.add(mask);
1728
1697
  this.canvas.bringToFront(mask);
1729
- if (maskConfig.selectable)
1730
- this.canvas.setActiveObject(mask);
1698
+ if (maskConfig.selectable) this.canvas.setActiveObject(mask);
1731
1699
  this._handleSelectionChanged([mask]);
1732
1700
  this._updateMaskList();
1733
1701
  this._updateUI();
1734
1702
  this.canvas.renderAll();
1735
1703
  this.saveState();
1736
- if (typeof maskConfig.onCreate === "function")
1737
- maskConfig.onCreate(mask, this.canvas);
1704
+ if (typeof maskConfig.onCreate === "function") maskConfig.onCreate(mask, this.canvas);
1738
1705
  return mask;
1739
1706
  }
1740
1707
  /**
@@ -1754,8 +1721,7 @@
1754
1721
  removeSelectedMask() {
1755
1722
  const activeObject = this.canvas.getActiveObject();
1756
1723
  const selectedMasks = this._getModifiedMasks(activeObject);
1757
- if (!selectedMasks.length)
1758
- return;
1724
+ if (!selectedMasks.length) return;
1759
1725
  this.canvas.discardActiveObject();
1760
1726
  selectedMasks.forEach((mask) => {
1761
1727
  this._removeLabelForMask(mask);
@@ -1790,8 +1756,7 @@
1790
1756
  this._updateMaskList();
1791
1757
  this._updateUI();
1792
1758
  this.canvas.renderAll();
1793
- if (saveHistory)
1794
- this.saveState();
1759
+ if (saveHistory) this.saveState();
1795
1760
  }
1796
1761
  /**
1797
1762
  * Removes the label associated with the specified mask object, if it exists.
@@ -1800,8 +1765,7 @@
1800
1765
  * @private
1801
1766
  */
1802
1767
  _removeLabelForMask(mask) {
1803
- if (!mask || !this.canvas)
1804
- return;
1768
+ if (!mask || !this.canvas) return;
1805
1769
  if (mask.__label) {
1806
1770
  try {
1807
1771
  const canvasObjects = this.canvas.getObjects();
@@ -1809,10 +1773,12 @@
1809
1773
  this.canvas.remove(mask.__label);
1810
1774
  }
1811
1775
  } catch (error) {
1776
+ void error;
1812
1777
  }
1813
1778
  try {
1814
1779
  delete mask.__label;
1815
1780
  } catch (error) {
1781
+ void error;
1816
1782
  }
1817
1783
  }
1818
1784
  }
@@ -1828,8 +1794,7 @@
1828
1794
  */
1829
1795
  _getMaskCreationIndex(mask) {
1830
1796
  const maskId = Number(mask && mask.maskId);
1831
- if (Number.isFinite(maskId) && maskId > 0)
1832
- return Math.floor(maskId) - 1;
1797
+ if (Number.isFinite(maskId) && maskId > 0) return Math.floor(maskId) - 1;
1833
1798
  const masks = this.canvas ? this.canvas.getObjects().filter((object) => object.maskId) : [];
1834
1799
  return Math.max(0, masks.indexOf(mask));
1835
1800
  }
@@ -1841,8 +1806,7 @@
1841
1806
  * @private
1842
1807
  */
1843
1808
  _createLabelForMask(mask) {
1844
- if (!mask || !this.options.maskLabelOnSelect)
1845
- return;
1809
+ if (!mask || !this.options.maskLabelOnSelect) return;
1846
1810
  this._removeLabelForMask(mask);
1847
1811
  let textObject = null;
1848
1812
  if (this.options.label && typeof this.options.label.create === "function") {
@@ -1884,15 +1848,14 @@
1884
1848
  * @private
1885
1849
  */
1886
1850
  _hideAllMaskLabels() {
1887
- if (!this.canvas)
1888
- return;
1851
+ if (!this.canvas) return;
1889
1852
  const canvasObjects = this.canvas.getObjects();
1890
1853
  const labels = canvasObjects.filter((object) => object.maskLabel);
1891
1854
  labels.forEach((label) => {
1892
1855
  try {
1893
- if (canvasObjects.includes(label))
1894
- this.canvas.remove(label);
1856
+ if (canvasObjects.includes(label)) this.canvas.remove(label);
1895
1857
  } catch (error) {
1858
+ void error;
1896
1859
  }
1897
1860
  });
1898
1861
  canvasObjects.forEach((object) => {
@@ -1900,6 +1863,7 @@
1900
1863
  try {
1901
1864
  delete object.__label;
1902
1865
  } catch (error) {
1866
+ void error;
1903
1867
  }
1904
1868
  }
1905
1869
  });
@@ -1911,15 +1875,11 @@
1911
1875
  * @private
1912
1876
  */
1913
1877
  _syncMaskLabel(mask) {
1914
- if (!mask)
1915
- return;
1916
- if (!this.options.maskLabelOnSelect)
1917
- return;
1918
- if (!mask.__label)
1919
- return;
1878
+ if (!mask) return;
1879
+ if (!this.options.maskLabelOnSelect) return;
1880
+ if (!mask.__label) return;
1920
1881
  const coords = mask.getCoords ? mask.getCoords() : null;
1921
- if (!coords || coords.length < 4)
1922
- return;
1882
+ if (!coords || coords.length < 4) return;
1923
1883
  const tl = coords[0];
1924
1884
  const center = mask.getCenterPoint();
1925
1885
  const vx = center.x - tl.x;
@@ -1952,12 +1912,9 @@
1952
1912
  * @private
1953
1913
  */
1954
1914
  _showLabelForMask(mask) {
1955
- if (!mask)
1956
- return;
1957
- if (!this.options.maskLabelOnSelect)
1958
- return;
1959
- if (!mask.__label)
1960
- this._createLabelForMask(mask);
1915
+ if (!mask) return;
1916
+ if (!this.options.maskLabelOnSelect) return;
1917
+ if (!mask.__label) this._createLabelForMask(mask);
1961
1918
  mask.__label.set({ visible: true });
1962
1919
  this._syncMaskLabel(mask);
1963
1920
  }
@@ -1977,6 +1934,7 @@
1977
1934
  try {
1978
1935
  this.canvas.remove(mask.__label);
1979
1936
  } catch (error) {
1937
+ void error;
1980
1938
  }
1981
1939
  delete mask.__label;
1982
1940
  }
@@ -1989,8 +1947,7 @@
1989
1947
  mask.set({ stroke: "#ff0000", strokeWidth: 1 });
1990
1948
  }
1991
1949
  });
1992
- if (selectedMask)
1993
- this._showLabelForMask(selectedMask);
1950
+ if (selectedMask) this._showLabelForMask(selectedMask);
1994
1951
  this._updateMaskListSelection(selectedMask);
1995
1952
  this.canvas.renderAll();
1996
1953
  this._updateUI();
@@ -2002,8 +1959,7 @@
2002
1959
  */
2003
1960
  _updateMaskList() {
2004
1961
  const maskListElement = document.getElementById(this.elements.maskList);
2005
- if (!maskListElement)
2006
- return;
1962
+ if (!maskListElement) return;
2007
1963
  maskListElement.innerHTML = "";
2008
1964
  const masks = this.canvas.getObjects().filter((object) => object.maskId);
2009
1965
  masks.forEach((mask) => {
@@ -2025,8 +1981,7 @@
2025
1981
  */
2026
1982
  _updateMaskListSelection(selectedMask) {
2027
1983
  const maskListElement = document.getElementById(this.elements.maskList);
2028
- if (!maskListElement)
2029
- return;
1984
+ if (!maskListElement) return;
2030
1985
  const maskItems = maskListElement.querySelectorAll(".mask-item");
2031
1986
  maskItems.forEach((item) => {
2032
1987
  const isSelected = !!selectedMask && item.textContent === selectedMask.maskName;
@@ -2044,11 +1999,9 @@
2044
1999
  * @public
2045
2000
  */
2046
2001
  async mergeMasks() {
2047
- if (!this.originalImage)
2048
- return;
2002
+ if (!this.originalImage) return;
2049
2003
  const masks = this.canvas.getObjects().filter((object) => object.maskId);
2050
- if (!masks.length)
2051
- return;
2004
+ if (!masks.length) return;
2052
2005
  this.canvas.discardActiveObject();
2053
2006
  this.canvas.renderAll();
2054
2007
  try {
@@ -2080,8 +2033,7 @@
2080
2033
  * @public
2081
2034
  */
2082
2035
  downloadImage(fileName = this.options.defaultDownloadFileName) {
2083
- if (!this.originalImage)
2084
- return;
2036
+ if (!this.originalImage) return;
2085
2037
  const exportImageArea = this.options.exportImageAreaByDefault;
2086
2038
  this.exportImageBase64({ exportImageArea, multiplier: this.options.exportMultiplier }).then((imageBase64) => {
2087
2039
  const link = document.createElement("a");
@@ -2109,8 +2061,7 @@
2109
2061
  * @public
2110
2062
  */
2111
2063
  async exportImageBase64(options = {}) {
2112
- if (!this.originalImage)
2113
- throw new Error("No image loaded");
2064
+ if (!this.originalImage) throw new Error("No image loaded");
2114
2065
  const exportImageArea = typeof options.exportImageArea === "boolean" ? options.exportImageArea : this.options.exportImageAreaByDefault;
2115
2066
  const multiplier = options.multiplier || this.options.exportMultiplier || 1;
2116
2067
  const quality = this._normalizeQuality(options.quality ?? this.options.downsampleQuality);
@@ -2138,6 +2089,7 @@
2138
2089
  try {
2139
2090
  backup.object.set({ visible: backup.visible });
2140
2091
  } catch (error) {
2092
+ void error;
2141
2093
  }
2142
2094
  });
2143
2095
  this.canvas.renderAll();
@@ -2185,6 +2137,7 @@
2185
2137
  });
2186
2138
  backup.object.setCoords();
2187
2139
  } catch (error) {
2140
+ void error;
2188
2141
  }
2189
2142
  });
2190
2143
  this.canvas.renderAll();
@@ -2220,8 +2173,7 @@
2220
2173
  * const file = await this.exportImageFile({ mergeMask: false, fileType: 'png' });
2221
2174
  */
2222
2175
  async exportImageFile(options = {}) {
2223
- if (!this.originalImage)
2224
- throw new Error("No image loaded");
2176
+ if (!this.originalImage) throw new Error("No image loaded");
2225
2177
  const {
2226
2178
  mergeMask = true,
2227
2179
  fileType = "jpeg",
@@ -2285,8 +2237,7 @@
2285
2237
  }
2286
2238
  async _restoreStateAfterCropFailure(beforeJson, message, error) {
2287
2239
  this._reportError(message, error);
2288
- if (this._cropRect && this.canvas)
2289
- this._removeCropRect();
2240
+ if (this._cropRect && this.canvas) this._removeCropRect();
2290
2241
  this._cropRect = null;
2291
2242
  this._cropMode = false;
2292
2243
  if (this.canvas && this._prevSelectionSetting !== void 0) {
@@ -2301,8 +2252,7 @@
2301
2252
  }
2302
2253
  }
2303
2254
  this._updateUI();
2304
- if (this.canvas)
2305
- this.canvas.renderAll();
2255
+ if (this.canvas) this.canvas.renderAll();
2306
2256
  }
2307
2257
  _restoreCropObjectState() {
2308
2258
  if (Array.isArray(this._cropPrevEvented)) {
@@ -2314,14 +2264,14 @@
2314
2264
  visible: state.visible
2315
2265
  });
2316
2266
  } catch (error) {
2267
+ void error;
2317
2268
  }
2318
2269
  });
2319
2270
  }
2320
2271
  this._cropPrevEvented = null;
2321
2272
  }
2322
2273
  _removeCropRect() {
2323
- if (!this._cropRect)
2324
- return;
2274
+ if (!this._cropRect) return;
2325
2275
  try {
2326
2276
  if (this._cropHandlers && this._cropHandlers.length) {
2327
2277
  this._cropHandlers.forEach((targetHandlers) => {
@@ -2331,10 +2281,12 @@
2331
2281
  });
2332
2282
  }
2333
2283
  } catch (error) {
2284
+ void error;
2334
2285
  }
2335
2286
  try {
2336
2287
  this.canvas.remove(this._cropRect);
2337
2288
  } catch (error) {
2289
+ void error;
2338
2290
  }
2339
2291
  this._cropRect = null;
2340
2292
  this._cropHandlers = [];
@@ -2349,10 +2301,8 @@
2349
2301
  * @public
2350
2302
  */
2351
2303
  enterCropMode() {
2352
- if (!this.canvas || !this.originalImage || this._cropMode)
2353
- return;
2354
- if (!this.isImageLoaded())
2355
- return;
2304
+ if (!this.canvas || !this.originalImage || this._cropMode) return;
2305
+ if (!this.isImageLoaded()) return;
2356
2306
  this._cropMode = true;
2357
2307
  this._prevSelectionSetting = this.canvas.selection;
2358
2308
  this.canvas.selection = false;
@@ -2404,10 +2354,10 @@
2404
2354
  evented: false,
2405
2355
  selectable: false
2406
2356
  };
2407
- if (shouldHideMasks && (object.maskId || object.maskLabel))
2408
- updates.visible = false;
2357
+ if (shouldHideMasks && (object.maskId || object.maskLabel)) updates.visible = false;
2409
2358
  object.set(updates);
2410
2359
  } catch (error) {
2360
+ void error;
2411
2361
  }
2412
2362
  }
2413
2363
  });
@@ -2421,6 +2371,7 @@
2421
2371
  cropRect.setCoords();
2422
2372
  this.canvas.requestRenderAll();
2423
2373
  } catch (error) {
2374
+ void error;
2424
2375
  }
2425
2376
  };
2426
2377
  cropRect.on("modified", handleCropRectModified);
@@ -2444,8 +2395,7 @@
2444
2395
  * @public
2445
2396
  */
2446
2397
  cancelCrop() {
2447
- if (!this.canvas || !this._cropMode)
2448
- return;
2398
+ if (!this.canvas || !this._cropMode) return;
2449
2399
  this._removeCropRect();
2450
2400
  this._restoreCropObjectState();
2451
2401
  this._cropMode = false;
@@ -2467,14 +2417,13 @@
2467
2417
  * @public
2468
2418
  */
2469
2419
  async applyCrop() {
2470
- if (!this.canvas || !this._cropMode || !this._cropRect)
2471
- return;
2420
+ if (!this.canvas || !this._cropMode || !this._cropRect) return;
2472
2421
  this._cropRect.setCoords();
2473
2422
  const rectBounds = this._cropRect.getBoundingRect(true, true);
2474
- const cropRegion = this._getClampedCanvasRegion(rectBounds);
2423
+ const cropRegion = this._getClampedCanvasRegion(rectBounds, { includePartialPixels: false });
2475
2424
  const shouldPreserveMasks = !!(this.options.crop && this.options.crop.preserveMasksAfterCrop);
2476
2425
  this._restoreCropObjectState();
2477
- let beforeJson = null;
2426
+ let beforeJson;
2478
2427
  try {
2479
2428
  beforeJson = this._serializeCanvasState();
2480
2429
  } catch (error) {
@@ -2545,7 +2494,7 @@
2545
2494
  await this._restoreStateAfterCropFailure(beforeJson, "applyCrop: loadImage(croppedBase64) failed", error);
2546
2495
  return;
2547
2496
  }
2548
- let afterJson = null;
2497
+ let afterJson;
2549
2498
  try {
2550
2499
  afterJson = this._serializeCanvasState();
2551
2500
  } catch (error) {
@@ -2568,8 +2517,7 @@
2568
2517
  */
2569
2518
  _updateInputs() {
2570
2519
  const scaleInputElement = document.getElementById(this.elements.scaleRate);
2571
- if (scaleInputElement)
2572
- scaleInputElement.value = Math.round(this.currentScale * 100);
2520
+ if (scaleInputElement) scaleInputElement.value = Math.round(this.currentScale * 100);
2573
2521
  }
2574
2522
  /**
2575
2523
  * Updates the enabled/disabled state of various UI controls (buttons)
@@ -2589,8 +2537,7 @@
2589
2537
  if (isInCropMode) {
2590
2538
  for (const key of Object.keys(this.elements || {})) {
2591
2539
  const element = document.getElementById(this.elements[key]);
2592
- if (!element)
2593
- continue;
2540
+ if (!element) continue;
2594
2541
  if (key === "applyCropBtn" || key === "cancelCropBtn") {
2595
2542
  this._setDisabled(key, false);
2596
2543
  } else {
@@ -2626,8 +2573,7 @@
2626
2573
  */
2627
2574
  _setDisabled(key, disabled) {
2628
2575
  const element = document.getElementById(this.elements[key]);
2629
- if (!element)
2630
- return;
2576
+ if (!element) return;
2631
2577
  if ("disabled" in element) {
2632
2578
  element.disabled = !!disabled;
2633
2579
  return;
@@ -2641,10 +2587,8 @@
2641
2587
  }
2642
2588
  }
2643
2589
  _isElementDisabled(element) {
2644
- if (!element)
2645
- return false;
2646
- if ("disabled" in element)
2647
- return !!element.disabled;
2590
+ if (!element) return false;
2591
+ if ("disabled" in element) return !!element.disabled;
2648
2592
  return element.getAttribute("aria-disabled") === "true";
2649
2593
  }
2650
2594
  /**
@@ -2652,8 +2596,7 @@
2652
2596
  * @private
2653
2597
  */
2654
2598
  _updatePlaceholderStatus() {
2655
- if (!this.options.showPlaceholder)
2656
- return;
2599
+ if (!this.options.showPlaceholder) return;
2657
2600
  this._setPlaceholderVisible(!this.originalImage);
2658
2601
  }
2659
2602
  /**
@@ -2663,17 +2606,23 @@
2663
2606
  * @private
2664
2607
  */
2665
2608
  _setPlaceholderVisible(show) {
2666
- if (!this.placeholderElement || !this.containerElement)
2667
- return;
2668
- if (show) {
2669
- this.placeholderElement.classList.remove("d-none");
2670
- this.placeholderElement.classList.add("d-flex");
2671
- this.containerElement.classList.add("d-none");
2672
- } else {
2673
- this.placeholderElement.classList.remove("d-flex");
2674
- this.placeholderElement.classList.add("d-none");
2675
- this.containerElement.classList.remove("d-none");
2676
- }
2609
+ if (!this.placeholderElement || !this.containerElement) return;
2610
+ this._setElementVisible(this.placeholderElement, show);
2611
+ this._setElementVisible(this.containerElement, !show);
2612
+ }
2613
+ /**
2614
+ * Updates element visibility.
2615
+ *
2616
+ * @param {HTMLElement} element - Element whose visibility should be updated.
2617
+ * @param {boolean} isVisible - If true, removes the hidden state.
2618
+ * @returns {void}
2619
+ * @private
2620
+ */
2621
+ _setElementVisible(element, isVisible) {
2622
+ if (!element) return;
2623
+ element.hidden = !isVisible;
2624
+ element.setAttribute("aria-hidden", isVisible ? "false" : "true");
2625
+ if (isVisible && element.classList) element.classList.remove("d-none");
2677
2626
  }
2678
2627
  /**
2679
2628
  * Cleans up and disposes of the canvas and related references.
@@ -2685,21 +2634,23 @@
2685
2634
  for (const key in this._handlersByElementKey || {}) {
2686
2635
  const handlers = this._handlersByElementKey[key] || [];
2687
2636
  const element = document.getElementById(this.elements[key]);
2688
- if (!element)
2689
- continue;
2637
+ if (!element) continue;
2690
2638
  handlers.forEach((handlerRecord) => {
2691
2639
  try {
2692
2640
  element.removeEventListener(handlerRecord.eventName, handlerRecord.handler);
2693
2641
  } catch (error) {
2642
+ void error;
2694
2643
  }
2695
2644
  });
2696
2645
  }
2697
2646
  } catch (error) {
2647
+ void error;
2698
2648
  }
2699
2649
  if (this._cropRect) {
2700
2650
  try {
2701
2651
  this.canvas.remove(this._cropRect);
2702
2652
  } catch (error) {
2653
+ void error;
2703
2654
  }
2704
2655
  this._cropRect = null;
2705
2656
  }
@@ -2707,12 +2658,14 @@
2707
2658
  try {
2708
2659
  this.containerElement.style.overflow = this._containerOriginalOverflow;
2709
2660
  } catch (error) {
2661
+ void error;
2710
2662
  }
2711
2663
  }
2712
2664
  if (this.canvas) {
2713
2665
  try {
2714
2666
  this.canvas.dispose();
2715
2667
  } catch (error) {
2668
+ void error;
2716
2669
  }
2717
2670
  this.canvas = null;
2718
2671
  this.canvasElement = null;