@eagami/ui 5.15.0 → 5.15.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.
@@ -5827,6 +5827,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
5827
5827
  }]
5828
5828
  }], ctorParameters: () => [], propDecorators: { eaTooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "eaTooltip", required: true }] }], tooltipPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltipPosition", required: false }] }], maxWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxWidth", required: false }] }] } });
5829
5829
 
5830
+ const ZOOM_TOLERANCE = 1e-3;
5831
+ const OFFSET_TOLERANCE = 0.5;
5830
5832
  /**
5831
5833
  * Canvas-based image editor for cropping avatars. Supports drag-and-drop
5832
5834
  * upload, mouse/touch panning, zoom via slider or scroll wheel, and exports
@@ -5883,7 +5885,7 @@ class AvatarEditorComponent {
5883
5885
  ...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
5884
5886
  zoom = signal(1, /* @ts-ignore */
5885
5887
  ...(ngDevMode ? [{ debugName: "zoom" }] : /* istanbul ignore next */ []));
5886
- canRevert = computed(() => !this.isAtOriginal() && this.originalCaptured, /* @ts-ignore */
5888
+ canRevert = computed(() => !this.isAtOriginal() && this.originalCaptured(), /* @ts-ignore */
5887
5889
  ...(ngDevMode ? [{ debugName: "canRevert" }] : /* istanbul ignore next */ []));
5888
5890
  /** True when the visible crop is darker than mid-grey; drives the `--on-light` class on the overlay so the "Change photo" affordance stays readable. */
5889
5891
  isImageDark = signal(true, /* @ts-ignore */
@@ -5900,7 +5902,8 @@ class AvatarEditorComponent {
5900
5902
  _suppressCropStateEmit = false;
5901
5903
  isFetching = signal(false, /* @ts-ignore */
5902
5904
  ...(ngDevMode ? [{ debugName: "isFetching" }] : /* istanbul ignore next */ []));
5903
- originalCaptured = false;
5905
+ originalCaptured = signal(false, /* @ts-ignore */
5906
+ ...(ngDevMode ? [{ debugName: "originalCaptured" }] : /* istanbul ignore next */ []));
5904
5907
  originalImage = null;
5905
5908
  originalCropState = null;
5906
5909
  hostClasses = computed(() => ({
@@ -5916,15 +5919,15 @@ class AvatarEditorComponent {
5916
5919
  const src = this.currentSrc();
5917
5920
  if (!src) {
5918
5921
  this.isFetching.set(false);
5919
- if (!this.originalCaptured) {
5920
- this.originalCaptured = true;
5922
+ if (!this.originalCaptured()) {
5923
+ this.originalCaptured.set(true);
5921
5924
  this.originalImage = null;
5922
5925
  this.originalCropState = null;
5923
5926
  this.isAtOriginal.set(true);
5924
5927
  }
5925
5928
  return;
5926
5929
  }
5927
- this.originalCaptured = false;
5930
+ this.originalCaptured.set(false);
5928
5931
  this.originalImage = null;
5929
5932
  this.originalCropState = null;
5930
5933
  this.loadFromUrl(src, untracked(() => this.cropState()) ?? null, true);
@@ -5935,6 +5938,7 @@ class AvatarEditorComponent {
5935
5938
  // Offsets were computed against the previous frame, so a shrink would
5936
5939
  // otherwise leave the image drawn outside it with a gap showing
5937
5940
  this.clampOffset();
5941
+ untracked(() => this.syncAtOriginal());
5938
5942
  this.draw();
5939
5943
  }
5940
5944
  });
@@ -6020,11 +6024,11 @@ class AvatarEditorComponent {
6020
6024
  const dy = event.clientY - this.dragStartY;
6021
6025
  if (Math.abs(dx) > 3 || Math.abs(dy) > 3) {
6022
6026
  this.hasDragged = true;
6023
- this.isAtOriginal.set(false);
6024
6027
  }
6025
6028
  this.offsetX = this.initialOffsetX + dx;
6026
6029
  this.offsetY = this.initialOffsetY + dy;
6027
6030
  this.clampOffset();
6031
+ this.syncAtOriginal();
6028
6032
  this.draw();
6029
6033
  this.emitCropStateChange();
6030
6034
  }
@@ -6046,11 +6050,11 @@ class AvatarEditorComponent {
6046
6050
  const dy = touch.clientY - this.dragStartY;
6047
6051
  if (Math.abs(dx) > 3 || Math.abs(dy) > 3) {
6048
6052
  this.hasDragged = true;
6049
- this.isAtOriginal.set(false);
6050
6053
  }
6051
6054
  this.offsetX = this.initialOffsetX + dx;
6052
6055
  this.offsetY = this.initialOffsetY + dy;
6053
6056
  this.clampOffset();
6057
+ this.syncAtOriginal();
6054
6058
  this.draw();
6055
6059
  this.emitCropStateChange();
6056
6060
  }
@@ -6111,18 +6115,17 @@ class AvatarEditorComponent {
6111
6115
  }
6112
6116
  if (handled) {
6113
6117
  event.preventDefault();
6114
- this.isAtOriginal.set(false);
6115
6118
  this.clampOffset();
6119
+ this.syncAtOriginal();
6116
6120
  this.draw();
6117
6121
  this.emitCropStateChange();
6118
6122
  }
6119
6123
  }
6120
6124
  /** Sets the zoom level, clamped to the configured `minZoom`/`maxZoom` range. */
6121
6125
  setZoom(value) {
6122
- this.isAtOriginal.set(false);
6123
- const clamped = Math.min(this.maxZoom(), Math.max(this.minZoom(), value));
6124
- this.zoom.set(Math.round(clamped * 100) / 100);
6126
+ this.applyZoom(value);
6125
6127
  this.clampOffset();
6128
+ this.syncAtOriginal();
6126
6129
  this.draw();
6127
6130
  this.emitCropStateChange();
6128
6131
  }
@@ -6139,12 +6142,31 @@ class AvatarEditorComponent {
6139
6142
  this.offsetX = 0;
6140
6143
  this.offsetY = 0;
6141
6144
  this.clearCanvas();
6142
- this.isAtOriginal.set(this.originalCaptured && !this.originalImage);
6145
+ this.syncAtOriginal();
6143
6146
  this.removed.emit();
6144
6147
  }
6148
+ // Pan and zoom write to plain properties rather than signals, so the revert
6149
+ // control cannot derive its own state. Every handler that moves the crop calls
6150
+ // this instead, so returning to the committed values re-disables the control.
6151
+ // Offsets are recalculated on zoom and both values are floats, so the
6152
+ // comparison is a tolerance rather than equality
6153
+ syncAtOriginal() {
6154
+ if (!this.originalCaptured() || this.image !== this.originalImage) {
6155
+ this.isAtOriginal.set(false);
6156
+ return;
6157
+ }
6158
+ const crop = this.originalCropState;
6159
+ if (!crop) {
6160
+ this.isAtOriginal.set(!this.image);
6161
+ return;
6162
+ }
6163
+ this.isAtOriginal.set(Math.abs(this.zoom() - crop.zoom) < ZOOM_TOLERANCE &&
6164
+ Math.abs(this.offsetX - crop.offsetX) < OFFSET_TOLERANCE &&
6165
+ Math.abs(this.offsetY - crop.offsetY) < OFFSET_TOLERANCE);
6166
+ }
6145
6167
  /** Marks the current image and crop state as the baseline for {@link revertImage}. */
6146
6168
  captureOriginal() {
6147
- this.originalCaptured = true;
6169
+ this.originalCaptured.set(true);
6148
6170
  this.originalImage = this.image;
6149
6171
  this.originalCropState = this.image
6150
6172
  ? { zoom: this.zoom(), offsetX: this.offsetX, offsetY: this.offsetY }
@@ -6153,7 +6175,7 @@ class AvatarEditorComponent {
6153
6175
  }
6154
6176
  /** Restores the image and crop state captured by the most recent {@link captureOriginal}. */
6155
6177
  revertImage() {
6156
- if (!this.originalCaptured) {
6178
+ if (!this.originalCaptured()) {
6157
6179
  return;
6158
6180
  }
6159
6181
  if (this.originalImage) {
@@ -6161,7 +6183,7 @@ class AvatarEditorComponent {
6161
6183
  this.hasImage.set(true);
6162
6184
  const crop = this.originalCropState;
6163
6185
  if (crop) {
6164
- this.zoom.set(Math.min(this.maxZoom(), Math.max(this.minZoom(), crop.zoom)));
6186
+ this.applyZoom(crop.zoom);
6165
6187
  this.offsetX = crop.offsetX;
6166
6188
  this.offsetY = crop.offsetY;
6167
6189
  this.clampOffset();
@@ -6231,7 +6253,7 @@ class AvatarEditorComponent {
6231
6253
  this.image = img;
6232
6254
  this.hasImage.set(true);
6233
6255
  if (cropState) {
6234
- this.zoom.set(Math.min(this.maxZoom(), Math.max(this.minZoom(), cropState.zoom)));
6256
+ this.applyZoom(cropState.zoom);
6235
6257
  this.offsetX = cropState.offsetX;
6236
6258
  this.offsetY = cropState.offsetY;
6237
6259
  this.clampOffset();
@@ -6241,11 +6263,13 @@ class AvatarEditorComponent {
6241
6263
  this.centerImage();
6242
6264
  }
6243
6265
  if (suppressEmit) {
6244
- this.originalCaptured = true;
6266
+ this.originalCaptured.set(true);
6245
6267
  this.originalImage = img;
6246
- this.originalCropState = cropState
6247
- ? { ...cropState }
6248
- : { zoom: this.zoom(), offsetX: this.offsetX, offsetY: this.offsetY };
6268
+ this.originalCropState = {
6269
+ zoom: this.zoom(),
6270
+ offsetX: this.offsetX,
6271
+ offsetY: this.offsetY,
6272
+ };
6249
6273
  this.isAtOriginal.set(true);
6250
6274
  }
6251
6275
  this.scheduleDrawAfterRender();
@@ -6324,6 +6348,10 @@ class AvatarEditorComponent {
6324
6348
  drawH: this.image.height * scale,
6325
6349
  };
6326
6350
  }
6351
+ applyZoom(value) {
6352
+ const clamped = Math.min(this.maxZoom(), Math.max(this.minZoom(), value));
6353
+ this.zoom.set(Math.round(clamped * 100) / 100);
6354
+ }
6327
6355
  clampOffset() {
6328
6356
  if (!this.image) {
6329
6357
  return;