@acorex/components 22.1.0-next.9 → 22.1.0

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.
Files changed (63) hide show
  1. package/fesm2022/acorex-components-alert.mjs +2 -2
  2. package/fesm2022/acorex-components-alert.mjs.map +1 -1
  3. package/fesm2022/acorex-components-audio-wave.mjs +15 -0
  4. package/fesm2022/acorex-components-audio-wave.mjs.map +1 -1
  5. package/fesm2022/acorex-components-calendar.mjs +321 -118
  6. package/fesm2022/acorex-components-calendar.mjs.map +1 -1
  7. package/fesm2022/acorex-components-combo-box.mjs +259 -37
  8. package/fesm2022/acorex-components-combo-box.mjs.map +1 -1
  9. package/fesm2022/acorex-components-conversation-acorex-components-conversation-BtkyQUlh.mjs +16548 -0
  10. package/fesm2022/acorex-components-conversation-acorex-components-conversation-BtkyQUlh.mjs.map +1 -0
  11. package/fesm2022/acorex-components-conversation-composer-action-choice-dialog.component-D4ca9Mvt.mjs +51 -0
  12. package/fesm2022/acorex-components-conversation-composer-action-choice-dialog.component-D4ca9Mvt.mjs.map +1 -0
  13. package/fesm2022/acorex-components-conversation-image-picker-editor-dialog.component-QVs1XWYf.mjs +85 -0
  14. package/fesm2022/acorex-components-conversation-image-picker-editor-dialog.component-QVs1XWYf.mjs.map +1 -0
  15. package/fesm2022/{acorex-components-conversation-open-composer-picker-for-files-BuNEjswI.mjs → acorex-components-conversation-open-composer-picker-for-files-BKKO_8x6.mjs} +7 -20
  16. package/fesm2022/acorex-components-conversation-open-composer-picker-for-files-BKKO_8x6.mjs.map +1 -0
  17. package/fesm2022/acorex-components-conversation-picker-rename-file-dialog.component-BNKFb5yO.mjs +65 -0
  18. package/fesm2022/acorex-components-conversation-picker-rename-file-dialog.component-BNKFb5yO.mjs.map +1 -0
  19. package/fesm2022/acorex-components-conversation.mjs +1 -17608
  20. package/fesm2022/acorex-components-conversation.mjs.map +1 -1
  21. package/fesm2022/acorex-components-data-table.mjs +57 -23
  22. package/fesm2022/acorex-components-data-table.mjs.map +1 -1
  23. package/fesm2022/acorex-components-decorators.mjs +2 -2
  24. package/fesm2022/acorex-components-decorators.mjs.map +1 -1
  25. package/fesm2022/acorex-components-dropdown.mjs +2 -2
  26. package/fesm2022/acorex-components-dropdown.mjs.map +1 -1
  27. package/fesm2022/acorex-components-fab.mjs +5 -5
  28. package/fesm2022/acorex-components-fab.mjs.map +1 -1
  29. package/fesm2022/acorex-components-image-editor.mjs +600 -409
  30. package/fesm2022/acorex-components-image-editor.mjs.map +1 -1
  31. package/fesm2022/acorex-components-lookup.mjs +1259 -169
  32. package/fesm2022/acorex-components-lookup.mjs.map +1 -1
  33. package/fesm2022/acorex-components-menu.mjs +11 -5
  34. package/fesm2022/acorex-components-menu.mjs.map +1 -1
  35. package/fesm2022/acorex-components-notification.mjs +6 -6
  36. package/fesm2022/acorex-components-notification.mjs.map +1 -1
  37. package/fesm2022/acorex-components-popover.mjs +33 -0
  38. package/fesm2022/acorex-components-popover.mjs.map +1 -1
  39. package/fesm2022/acorex-components-rail-navigation.mjs +2 -2
  40. package/fesm2022/acorex-components-rail-navigation.mjs.map +1 -1
  41. package/fesm2022/acorex-components-range-slider.mjs +2 -2
  42. package/fesm2022/acorex-components-range-slider.mjs.map +1 -1
  43. package/fesm2022/acorex-components-select-box.mjs +38 -11
  44. package/fesm2022/acorex-components-select-box.mjs.map +1 -1
  45. package/fesm2022/acorex-components-tabs.mjs +2 -2
  46. package/fesm2022/acorex-components-tabs.mjs.map +1 -1
  47. package/fesm2022/acorex-components-tree-view.mjs +45 -3
  48. package/fesm2022/acorex-components-tree-view.mjs.map +1 -1
  49. package/lookup/README.md +19 -8
  50. package/package.json +4 -3
  51. package/types/acorex-components-audio-wave.d.ts +3 -0
  52. package/types/acorex-components-calendar.d.ts +41 -9
  53. package/types/acorex-components-combo-box.d.ts +50 -10
  54. package/types/acorex-components-conversation.d.ts +1232 -1782
  55. package/types/acorex-components-data-table.d.ts +12 -2
  56. package/types/acorex-components-image-editor.d.ts +54 -49
  57. package/types/acorex-components-lookup.d.ts +286 -71
  58. package/types/acorex-components-popover.d.ts +8 -0
  59. package/types/acorex-components-select-box.d.ts +11 -0
  60. package/types/acorex-components-tree-view.d.ts +11 -1
  61. package/fesm2022/acorex-components-conversation-composer-action-choice-dialog.component-DCcRJYfF.mjs +0 -50
  62. package/fesm2022/acorex-components-conversation-composer-action-choice-dialog.component-DCcRJYfF.mjs.map +0 -1
  63. package/fesm2022/acorex-components-conversation-open-composer-picker-for-files-BuNEjswI.mjs.map +0 -1
@@ -1,24 +1,21 @@
1
1
  import { AXComponent, MXBaseComponent, MXInputBaseValueComponent, MXLookComponent, AXFocusableComponent, AXValuableComponent } from '@acorex/cdk/common';
2
2
  import * as i1 from '@acorex/components/decorators';
3
3
  import { AXDecoratorModule, AXDecoratorIconComponent } from '@acorex/components/decorators';
4
- import { AXLoadingSpinnerComponent, AXLoadingModule } from '@acorex/components/loading';
4
+ import { AXLoadingSpinnerComponent } from '@acorex/components/loading';
5
+ import { AXTranslatorPipe } from '@acorex/core/translation';
6
+ import { isPlatformBrowser, AsyncPipe } from '@angular/common';
5
7
  import * as i0 from '@angular/core';
6
- import { signal, Injectable, input, inject, NgZone, ElementRef, viewChild, Renderer2, PLATFORM_ID, effect, afterNextRender, ViewEncapsulation, Component, linkedSignal, contentChild, forwardRef, HostBinding, output, computed, NgModule } from '@angular/core';
8
+ import { signal, Injectable, input, inject, NgZone, ElementRef, viewChild, Renderer2, PLATFORM_ID, effect, untracked, afterNextRender, ChangeDetectionStrategy, ViewEncapsulation, Component, DestroyRef, contentChild, forwardRef, output, computed, NgModule } from '@angular/core';
7
9
  import * as i1$1 from '@angular/forms';
8
10
  import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
9
11
  import { classes } from 'polytype';
10
- import { isPlatformBrowser, AsyncPipe } from '@angular/common';
11
- import { AXButtonComponent, AXButtonModule } from '@acorex/components/button';
12
+ import { AXButtonComponent } from '@acorex/components/button';
12
13
  import * as i2 from '@acorex/components/tooltip';
13
14
  import { AXTooltipModule, AXTooltipDirective } from '@acorex/components/tooltip';
14
- import { AXTranslatorPipe } from '@acorex/core/translation';
15
15
  import * as i2$1 from '@acorex/components/toolbar';
16
16
  import { AXToolBarModule } from '@acorex/components/toolbar';
17
- import { AXColorBoxComponent, AXColorBoxModule } from '@acorex/components/color-box';
18
- import { AXRangeSliderComponent, AXRangeSliderModule } from '@acorex/components/range-slider';
19
- import { AXDialogModule } from '@acorex/components/dialog';
20
- import { AXPopoverModule } from '@acorex/components/popover';
21
- import { AXSelectBoxModule } from '@acorex/components/select-box';
17
+ import { AXColorBoxComponent } from '@acorex/components/color-box';
18
+ import { AXRangeSliderComponent } from '@acorex/components/range-slider';
22
19
 
23
20
  class AXImageEditorService {
24
21
  constructor() {
@@ -38,6 +35,8 @@ class AXImageEditorService {
38
35
  ...(ngDevMode ? [{ debugName: "rotate" }] : /* istanbul ignore next */ []));
39
36
  this.isImageLoad = signal(false, /* @ts-ignore */
40
37
  ...(ngDevMode ? [{ debugName: "isImageLoad" }] : /* istanbul ignore next */ []));
38
+ this.loadError = signal(false, /* @ts-ignore */
39
+ ...(ngDevMode ? [{ debugName: "loadError" }] : /* istanbul ignore next */ []));
41
40
  this.newImage = null;
42
41
  this.initialImage = null;
43
42
  this.imageBlob = signal([], /* @ts-ignore */
@@ -46,6 +45,10 @@ class AXImageEditorService {
46
45
  ...(ngDevMode ? [{ debugName: "undoPressed" }] : /* istanbul ignore next */ []));
47
46
  this.redoPressed = signal(false, /* @ts-ignore */
48
47
  ...(ngDevMode ? [{ debugName: "redoPressed" }] : /* istanbul ignore next */ []));
48
+ this.canUndo = signal(false, /* @ts-ignore */
49
+ ...(ngDevMode ? [{ debugName: "canUndo" }] : /* istanbul ignore next */ []));
50
+ this.canRedo = signal(false, /* @ts-ignore */
51
+ ...(ngDevMode ? [{ debugName: "canRedo" }] : /* istanbul ignore next */ []));
49
52
  this.cropperRatio = signal(null, /* @ts-ignore */
50
53
  ...(ngDevMode ? [{ debugName: "cropperRatio" }] : /* istanbul ignore next */ []));
51
54
  this.cropperSize = signal(150, /* @ts-ignore */
@@ -54,6 +57,7 @@ class AXImageEditorService {
54
57
  toggleTool(tool) {
55
58
  if (this.activeToolState() === tool) {
56
59
  this.activeToolState.set(null);
60
+ this.penType.set(null);
57
61
  return;
58
62
  }
59
63
  this.activateTool(tool);
@@ -62,7 +66,21 @@ class AXImageEditorService {
62
66
  this.activeToolState.set(tool);
63
67
  if (tool === 'pen' || tool === 'highlight') {
64
68
  this.penType.set(tool);
69
+ return;
70
+ }
71
+ this.penType.set(null);
72
+ }
73
+ undo() {
74
+ if (!this.canUndo()) {
75
+ return;
76
+ }
77
+ this.undoPressed.set(true);
78
+ }
79
+ redo() {
80
+ if (!this.canRedo()) {
81
+ return;
65
82
  }
83
+ this.redoPressed.set(true);
66
84
  }
67
85
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXImageEditorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
68
86
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXImageEditorService }); }
@@ -91,63 +109,42 @@ class AXImageEditorCropperWindowComponent {
91
109
  this.elementRef = inject(ElementRef);
92
110
  this.cropperWindow = viewChild('r', /* @ts-ignore */
93
111
  ...(ngDevMode ? [{ debugName: "cropperWindow" }] : /* istanbul ignore next */ []));
94
- this.currentX = signal(-45, /* @ts-ignore */
112
+ this.currentX = signal(0, /* @ts-ignore */
95
113
  ...(ngDevMode ? [{ debugName: "currentX" }] : /* istanbul ignore next */ []));
96
- this.currentY = signal(-45, /* @ts-ignore */
114
+ this.currentY = signal(0, /* @ts-ignore */
97
115
  ...(ngDevMode ? [{ debugName: "currentY" }] : /* istanbul ignore next */ []));
98
- this.prevX = signal(0, /* @ts-ignore */
99
- ...(ngDevMode ? [{ debugName: "prevX" }] : /* istanbul ignore next */ []));
100
- this.prevY = signal(0, /* @ts-ignore */
101
- ...(ngDevMode ? [{ debugName: "prevY" }] : /* istanbul ignore next */ []));
102
- this.aspectWidth = signal(0, /* @ts-ignore */
116
+ this.aspectWidth = signal(1, /* @ts-ignore */
103
117
  ...(ngDevMode ? [{ debugName: "aspectWidth" }] : /* istanbul ignore next */ []));
104
- this.aspectHeight = signal(0, /* @ts-ignore */
118
+ this.aspectHeight = signal(1, /* @ts-ignore */
105
119
  ...(ngDevMode ? [{ debugName: "aspectHeight" }] : /* istanbul ignore next */ []));
106
120
  this.isMousePressed = signal(false, /* @ts-ignore */
107
121
  ...(ngDevMode ? [{ debugName: "isMousePressed" }] : /* istanbul ignore next */ []));
108
122
  this.renderer = inject(Renderer2);
109
123
  this.platformID = inject(PLATFORM_ID);
110
- this.#eff = effect(() => {
124
+ this.positioned = false;
125
+ this.#sizeEffect = effect(() => {
111
126
  const host = this.elementRef.nativeElement;
112
- let width;
113
- let height;
114
127
  const scale = this.size() ?? this.service.cropperSize();
115
- switch (this.service.cropperRatio()) {
116
- case '1:1':
117
- width = 1;
118
- height = 1;
119
- break;
120
- case '16:9':
121
- width = 1.777777777777778;
122
- height = 1;
123
- break;
124
- case null:
125
- width = 1;
126
- height = 1;
127
- break;
128
- default:
129
- [width, height] = this.service.cropperRatio().split(':').map(Number);
130
- }
131
- if (width >= height) {
132
- const aspect = width / height;
133
- width = aspect;
134
- height = 1;
135
- this.aspectWidth.set(aspect);
136
- this.aspectHeight.set(1);
128
+ const ratio = this.service.cropperRatio();
129
+ const { width, height } = this.normalizeRatio(ratio);
130
+ this.aspectWidth.set(width);
131
+ this.aspectHeight.set(height);
132
+ const parent = host.parentElement;
133
+ const maxW = parent?.clientWidth || scale * width;
134
+ const maxH = parent?.clientHeight || scale * height;
135
+ const nextWidth = Math.min(width * scale, maxW * 0.9);
136
+ const nextHeight = (nextWidth * height) / width;
137
+ host.style.width = `${Math.max(20, nextWidth)}px`;
138
+ host.style.height = `${Math.max(20, Math.min(nextHeight, maxH * 0.9))}px`;
139
+ if (this.positioned) {
140
+ untracked(() => this.clampToParent());
137
141
  }
138
- else {
139
- const aspect = height / width;
140
- width = 1;
141
- height = aspect;
142
- this.aspectWidth.set(1);
143
- this.aspectHeight.set(aspect);
144
- }
145
- host.style.width = `${width * scale}px`;
146
- host.style.height = `${height * scale}px`;
147
142
  }, /* @ts-ignore */
148
- ...(ngDevMode ? [{ debugName: "#eff" }] : /* istanbul ignore next */ []));
143
+ ...(ngDevMode ? [{ debugName: "#sizeEffect" }] : /* istanbul ignore next */ []));
149
144
  this.#init = afterNextRender(() => {
150
145
  const host = this.elementRef.nativeElement;
146
+ this.centerOnParent();
147
+ this.positioned = true;
151
148
  this.ngZone.runOutsideAngular(() => {
152
149
  this.mouseMoveEvent = this.renderer.listen(document, 'pointermove', this.changeSizeHandler.bind(this));
153
150
  this.mouseUpEvent = this.renderer.listen(document, 'pointerup', this.clearSelector.bind(this));
@@ -156,34 +153,26 @@ class AXImageEditorCropperWindowComponent {
156
153
  this.mouseUpEvent2 = this.renderer.listen(host, 'pointerup', this.panUp.bind(this));
157
154
  });
158
155
  });
159
- this.#positionChange = effect(() => {
160
- this.setPosition(this.currentX(), this.currentY());
161
- }, /* @ts-ignore */
162
- ...(ngDevMode ? [{ debugName: "#positionChange" }] : /* istanbul ignore next */ []));
156
+ this.startX = 0;
157
+ this.startY = 0;
158
+ this.startWidth = 0;
159
+ this.startHeight = 0;
160
+ this.startLeft = 0;
161
+ this.startTop = 0;
163
162
  }
164
- #eff;
163
+ #sizeEffect;
165
164
  #init;
166
165
  ngOnDestroy() {
167
- if (isPlatformBrowser(this.platformID) && this.mouseMoveEvent) {
168
- this.mouseMoveEvent();
169
- }
170
- if (isPlatformBrowser(this.platformID) && this.mouseUpEvent) {
171
- this.mouseUpEvent();
172
- }
173
- if (isPlatformBrowser(this.platformID) && this.mouseDownEvent) {
174
- this.mouseDownEvent();
175
- }
176
- if (isPlatformBrowser(this.platformID) && this.mouseMoveEvent2) {
177
- this.mouseMoveEvent2();
178
- }
179
- if (isPlatformBrowser(this.platformID) && this.mouseUpEvent2) {
180
- this.mouseUpEvent2();
181
- }
182
- if (isPlatformBrowser(this.platformID) && this.mouseLeaveEvent) {
183
- this.mouseLeaveEvent();
166
+ if (!isPlatformBrowser(this.platformID)) {
167
+ return;
184
168
  }
169
+ this.mouseMoveEvent?.();
170
+ this.mouseUpEvent?.();
171
+ this.mouseDownEvent?.();
172
+ this.mouseMoveEvent2?.();
173
+ this.mouseUpEvent2?.();
174
+ this.mouseLeaveEvent?.();
185
175
  }
186
- #positionChange;
187
176
  panMove(e) {
188
177
  if (!this.isMousePressed())
189
178
  return;
@@ -191,39 +180,27 @@ class AXImageEditorCropperWindowComponent {
191
180
  return;
192
181
  if (!this.panState())
193
182
  return;
194
- const currentX = e.clientX;
195
- const currentY = e.clientY;
196
- const deltaX = currentX - this.prevX();
197
- const deltaY = currentY - this.prevY();
198
- this.currentX.update((prev) => prev + deltaX);
199
- this.currentY.update((prev) => prev + deltaY);
200
- this.prevX.set(currentX);
201
- this.prevY.set(currentY);
183
+ this.currentX.update((prev) => prev + e.movementX);
184
+ this.currentY.update((prev) => prev + e.movementY);
185
+ this.clampToParent();
202
186
  }
203
187
  panDown(e) {
204
- this.isMousePressed.set(true);
205
188
  if (this.selectedHandle())
206
189
  return;
190
+ this.isMousePressed.set(true);
207
191
  this.panState.set(true);
208
- this.prevX.set(e.clientX);
209
- this.prevY.set(e.clientY);
192
+ e.currentTarget?.setPointerCapture?.(e.pointerId);
210
193
  }
211
194
  panUp() {
212
195
  this.isMousePressed.set(false);
213
- if (this.selectedHandle())
214
- return;
215
196
  this.panState.set(false);
216
197
  }
217
198
  getCropperArea() {
218
199
  return this.elementRef.nativeElement.getBoundingClientRect();
219
200
  }
220
- setPosition(posX, posY) {
221
- const el = this.elementRef.nativeElement;
222
- el.style.transform = `translate(${posX}px, ${posY}px)`;
223
- }
224
201
  handleSelector(e, value) {
225
- if (this.panState())
226
- return;
202
+ e.stopPropagation();
203
+ e.preventDefault();
227
204
  const host = this.elementRef.nativeElement;
228
205
  this.startX = e.clientX;
229
206
  this.startY = e.clientY;
@@ -232,6 +209,8 @@ class AXImageEditorCropperWindowComponent {
232
209
  this.startLeft = host.offsetLeft;
233
210
  this.startTop = host.offsetTop;
234
211
  this.selectedHandle.set(value);
212
+ this.isMousePressed.set(true);
213
+ e.currentTarget?.setPointerCapture?.(e.pointerId);
235
214
  }
236
215
  changeSizeHandler(e) {
237
216
  if (!this.isMousePressed())
@@ -243,164 +222,176 @@ class AXImageEditorCropperWindowComponent {
243
222
  const host = this.elementRef.nativeElement;
244
223
  const dy = e.clientY - this.startY;
245
224
  const dx = e.clientX - this.startX;
246
- let newWidth;
247
- let newHeight;
248
225
  const minSize = 20;
226
+ const locked = !!this.service.cropperRatio();
227
+ const aspect = this.aspectWidth() / this.aspectHeight();
228
+ let width = this.startWidth;
229
+ let height = this.startHeight;
230
+ let left = this.startLeft;
231
+ let top = this.startTop;
249
232
  switch (this.selectedHandle()) {
250
233
  case 'right':
251
- newWidth = Math.max(minSize, this.startWidth + dx);
252
- host.style.width = `${newWidth}px`;
234
+ width = Math.max(minSize, this.startWidth + dx);
253
235
  break;
254
236
  case 'top':
255
- newHeight = Math.max(minSize, this.startHeight - dy);
256
- host.style.height = `${newHeight}px`;
257
- host.style.top = `${this.startTop + (this.startHeight - newHeight)}px`;
237
+ height = Math.max(minSize, this.startHeight - dy);
238
+ top = this.startTop + (this.startHeight - height);
258
239
  break;
259
240
  case 'left':
260
- newWidth = Math.max(minSize, this.startWidth - dx);
261
- host.style.width = `${newWidth}px`;
262
- host.style.left = `${this.startLeft + (this.startWidth - newWidth)}px`;
241
+ width = Math.max(minSize, this.startWidth - dx);
242
+ left = this.startLeft + (this.startWidth - width);
263
243
  break;
264
244
  case 'bottom':
265
- newHeight = Math.max(minSize, this.startHeight + dy);
266
- host.style.height = `${newHeight}px`;
245
+ height = Math.max(minSize, this.startHeight + dy);
267
246
  break;
268
247
  case 'right-top':
269
- if (this.service.cropperRatio()) {
270
- newWidth = Math.max(minSize, this.startWidth + dx);
271
- newHeight = (newWidth * this.aspectHeight()) / this.aspectWidth();
272
- if (newHeight < minSize) {
273
- newHeight = minSize;
274
- newWidth = (newHeight * this.aspectWidth()) / this.aspectHeight();
275
- }
276
- host.style.width = `${newWidth}px`;
277
- host.style.height = `${newHeight}px`;
278
- host.style.top = `${this.startTop + (this.startHeight - newHeight)}px`;
279
- }
280
- else {
281
- newWidth = Math.max(minSize, this.startWidth + dx);
282
- newHeight = Math.max(minSize, this.startHeight - dy);
283
- host.style.width = `${newWidth}px`;
284
- host.style.height = `${newHeight}px`;
285
- host.style.top = `${this.startTop + (this.startHeight - newHeight)}px`;
248
+ width = Math.max(minSize, this.startWidth + dx);
249
+ height = locked ? width / aspect : Math.max(minSize, this.startHeight - dy);
250
+ if (locked && height < minSize) {
251
+ height = minSize;
252
+ width = height * aspect;
286
253
  }
254
+ top = this.startTop + (this.startHeight - height);
287
255
  break;
288
256
  case 'bottom-right':
289
- if (this.service.cropperRatio()) {
290
- newWidth = Math.max(minSize, this.startWidth + dx);
291
- newHeight = (newWidth * this.aspectHeight()) / this.aspectWidth();
292
- if (newHeight < minSize) {
293
- newHeight = minSize;
294
- newWidth = (newHeight * this.aspectWidth()) / this.aspectHeight();
295
- }
296
- host.style.width = `${newWidth}px`;
297
- host.style.height = `${newHeight}px`;
298
- }
299
- else {
300
- newWidth = Math.max(minSize, this.startWidth + dx);
301
- newHeight = Math.max(minSize, this.startHeight + dy);
302
- host.style.width = `${newWidth}px`;
303
- host.style.height = `${newHeight}px`;
257
+ width = Math.max(minSize, this.startWidth + dx);
258
+ height = locked ? width / aspect : Math.max(minSize, this.startHeight + dy);
259
+ if (locked && height < minSize) {
260
+ height = minSize;
261
+ width = height * aspect;
304
262
  }
305
263
  break;
306
264
  case 'bottom-left':
307
- if (this.service.cropperRatio()) {
308
- newWidth = Math.max(minSize, this.startWidth - dx);
309
- newHeight = (newWidth * this.aspectHeight()) / this.aspectWidth();
310
- if (newHeight < minSize) {
311
- newHeight = minSize;
312
- newWidth = (newHeight * this.aspectWidth()) / this.aspectHeight();
313
- }
314
- host.style.width = `${newWidth}px`;
315
- host.style.height = `${newHeight}px`;
316
- host.style.left = `${this.startLeft + (this.startWidth - newWidth)}px`;
317
- }
318
- else {
319
- newWidth = Math.max(minSize, this.startWidth - dx);
320
- newHeight = Math.max(minSize, this.startHeight + dy);
321
- host.style.width = `${newWidth}px`;
322
- host.style.height = `${newHeight}px`;
323
- host.style.left = `${this.startLeft + (this.startWidth - newWidth)}px`;
265
+ width = Math.max(minSize, this.startWidth - dx);
266
+ height = locked ? width / aspect : Math.max(minSize, this.startHeight + dy);
267
+ if (locked && height < minSize) {
268
+ height = minSize;
269
+ width = height * aspect;
324
270
  }
271
+ left = this.startLeft + (this.startWidth - width);
325
272
  break;
326
273
  case 'left-top':
327
- if (this.service.cropperRatio()) {
328
- newWidth = Math.max(minSize, this.startWidth - dx);
329
- newHeight = (newWidth * this.aspectHeight()) / this.aspectWidth();
330
- if (newHeight < minSize) {
331
- newHeight = minSize;
332
- newWidth = (newHeight * this.aspectWidth()) / this.aspectHeight();
333
- }
334
- host.style.width = `${newWidth}px`;
335
- host.style.height = `${newHeight}px`;
336
- host.style.left = `${this.startLeft + (this.startWidth - newWidth)}px`;
337
- host.style.top = `${this.startTop + (this.startHeight - newHeight)}px`;
338
- }
339
- else {
340
- newWidth = Math.max(minSize, this.startWidth - dx);
341
- newHeight = Math.max(minSize, this.startHeight - dy);
342
- host.style.width = `${newWidth}px`;
343
- host.style.height = `${newHeight}px`;
344
- host.style.left = `${this.startLeft + (this.startWidth - newWidth)}px`;
345
- host.style.top = `${this.startTop + (this.startHeight - newHeight)}px`;
274
+ width = Math.max(minSize, this.startWidth - dx);
275
+ height = locked ? width / aspect : Math.max(minSize, this.startHeight - dy);
276
+ if (locked && height < minSize) {
277
+ height = minSize;
278
+ width = height * aspect;
346
279
  }
280
+ left = this.startLeft + (this.startWidth - width);
281
+ top = this.startTop + (this.startHeight - height);
347
282
  break;
348
283
  }
284
+ host.style.width = `${width}px`;
285
+ host.style.height = `${height}px`;
286
+ this.currentX.set(left);
287
+ this.currentY.set(top);
288
+ this.clampToParent();
349
289
  }
350
290
  clearSelector() {
351
- if (this.panState())
352
- return;
353
291
  this.selectedHandle.set(null);
292
+ this.isMousePressed.set(false);
293
+ this.panState.set(false);
294
+ }
295
+ normalizeRatio(ratio) {
296
+ let width = 1;
297
+ let height = 1;
298
+ if (ratio && ratio.includes(':')) {
299
+ const [rawW, rawH] = ratio.split(':').map(Number);
300
+ if (rawW > 0 && rawH > 0) {
301
+ width = rawW;
302
+ height = rawH;
303
+ }
304
+ }
305
+ if (width >= height) {
306
+ return { width: width / height, height: 1 };
307
+ }
308
+ return { width: 1, height: height / width };
309
+ }
310
+ centerOnParent() {
311
+ const host = this.elementRef.nativeElement;
312
+ const parent = host.parentElement;
313
+ if (!parent) {
314
+ return;
315
+ }
316
+ const left = Math.max(0, (parent.clientWidth - host.offsetWidth) / 2);
317
+ const top = Math.max(0, (parent.clientHeight - host.offsetHeight) / 2);
318
+ this.currentX.set(left);
319
+ this.currentY.set(top);
320
+ this.applyPosition();
321
+ }
322
+ clampToParent() {
323
+ const host = this.elementRef.nativeElement;
324
+ const parent = host.parentElement;
325
+ if (!parent) {
326
+ this.applyPosition();
327
+ return;
328
+ }
329
+ const maxX = Math.max(0, parent.clientWidth - host.offsetWidth);
330
+ const maxY = Math.max(0, parent.clientHeight - host.offsetHeight);
331
+ this.currentX.set(Math.min(Math.max(0, this.currentX()), maxX));
332
+ this.currentY.set(Math.min(Math.max(0, this.currentY()), maxY));
333
+ this.applyPosition();
334
+ }
335
+ applyPosition() {
336
+ const host = this.elementRef.nativeElement;
337
+ host.style.left = `${this.currentX()}px`;
338
+ host.style.top = `${this.currentY()}px`;
354
339
  }
355
340
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXImageEditorCropperWindowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
356
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXImageEditorCropperWindowComponent, isStandalone: true, selector: "ax-image-editor-cropper-window", inputs: { showGrid: { classPropertyName: "showGrid", publicName: "showGrid", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: AXComponent, useExisting: AXImageEditorCropperWindowComponent }], viewQueries: [{ propertyName: "cropperWindow", first: true, predicate: ["r"], descendants: true, isSignal: true }], ngImport: i0, template: "<div [style.border]=\"!showGrid() ? '1px dashed white' : null\" #r class=\"ax-image-cropper-window-container\">\n @if (!service.cropperRatio()) {\n <div (pointerdown)=\"handleSelector($event, 'right')\" class=\"ax-image-right-handle ax-handle-shape\"></div>\n <div (pointerdown)=\"handleSelector($event, 'top')\" class=\"ax-image-top-handle ax-handle-shape\"></div>\n <div (pointerdown)=\"handleSelector($event, 'left')\" class=\"ax-image-left-handle ax-handle-shape\"></div>\n <div (pointerdown)=\"handleSelector($event, 'bottom')\" class=\"ax-image-bottom-handle ax-handle-shape\"></div>\n }\n\n <div (pointerdown)=\"handleSelector($event, 'right-top')\" class=\"ax-image-right-top-handle ax-handle-shape\"></div>\n <div\n (pointerdown)=\"handleSelector($event, 'bottom-right')\"\n class=\"ax-image-bottom-right-handle ax-handle-shape\"\n ></div>\n <div (pointerdown)=\"handleSelector($event, 'bottom-left')\" class=\"ax-image-bottom-left-handle ax-handle-shape\"></div>\n <div (pointerdown)=\"handleSelector($event, 'left-top')\" class=\"ax-image-left-top-handle ax-handle-shape\"></div>\n\n @if (showGrid()) {\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n }\n</div>\n", styles: ["@layer properties;@layer components{ax-image-editor-cropper-window{position:absolute;top:45%;left:45%;touch-action:none;-webkit-user-select:none;user-select:none}ax-image-editor-cropper-window .ax-image-cropper-window-container{position:relative;display:grid;height:100%;cursor:move;grid-template-columns:repeat(3,minmax(0,1fr));outline-style:var(--tw-outline-style);outline-width:100vmax;outline-color:color-mix(in srgb,#000 70%,transparent);--tw-outline-style: solid;outline-style:solid}@supports (color: color-mix(in lab,red,red)){ax-image-editor-cropper-window .ax-image-cropper-window-container{outline-color:color-mix(in oklab,var(--color-black, #000) 70%,transparent)}}ax-image-editor-cropper-window .ax-image-cropper-window-container .ax-grid-item{border-style:var(--tw-border-style);border-width:1px;--tw-border-style: dashed;border-style:dashed;border-color:color-mix(in srgb,#fff 50%,transparent)}@supports (color: color-mix(in lab,red,red)){ax-image-editor-cropper-window .ax-image-cropper-window-container .ax-grid-item{border-color:color-mix(in oklab,var(--color-white, #fff) 50%,transparent)}}ax-image-editor-cropper-window .ax-handle-shape{position:absolute;height:calc(var(--spacing, .25rem) * 4);width:calc(var(--spacing, .25rem) * 4);border-style:var(--tw-border-style);border-width:1px;--tw-border-style: dashed;border-style:dashed;border-color:var(--color-white, #fff);background-color:var(--color-gray-500, oklch(55.1% .027 264.364));-webkit-user-select:none;user-select:none}ax-image-editor-cropper-window .ax-image-right-top-handle{top:calc(var(--spacing, .25rem) * -2);right:calc(var(--spacing, .25rem) * -2);cursor:ne-resize}ax-image-editor-cropper-window .ax-image-bottom-right-handle{right:calc(var(--spacing, .25rem) * -2);bottom:calc(var(--spacing, .25rem) * -2);cursor:se-resize}ax-image-editor-cropper-window .ax-image-right-handle{top:calc(50% - .5rem);right:calc(var(--spacing, .25rem) * -2);cursor:e-resize}ax-image-editor-cropper-window .ax-image-bottom-left-handle{bottom:calc(var(--spacing, .25rem) * -2);left:calc(var(--spacing, .25rem) * -2);cursor:sw-resize}ax-image-editor-cropper-window .ax-image-left-top-handle{top:calc(var(--spacing, .25rem) * -2);left:calc(var(--spacing, .25rem) * -2);cursor:nw-resize}ax-image-editor-cropper-window .ax-image-bottom-handle{right:calc(50% - .5rem);bottom:calc(var(--spacing, .25rem) * -2);cursor:s-resize}ax-image-editor-cropper-window .ax-image-left-handle{top:calc(50% - .5rem);left:calc(var(--spacing, .25rem) * -2);cursor:w-resize}ax-image-editor-cropper-window .ax-image-top-handle{top:calc(var(--spacing, .25rem) * -2);right:calc(50% - .5rem);cursor:n-resize}}@property --tw-outline-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-outline-style: solid;--tw-border-style: solid}}}\n/*! tailwindcss v4.1.16 | MIT License | https://tailwindcss.com */\n"], encapsulation: i0.ViewEncapsulation.None }); }
341
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXImageEditorCropperWindowComponent, isStandalone: true, selector: "ax-image-editor-cropper-window", inputs: { showGrid: { classPropertyName: "showGrid", publicName: "showGrid", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: AXComponent, useExisting: AXImageEditorCropperWindowComponent }], viewQueries: [{ propertyName: "cropperWindow", first: true, predicate: ["r"], descendants: true, isSignal: true }], ngImport: i0, template: "<div [style.border]=\"!showGrid() ? '1px dashed white' : null\" #r class=\"ax-image-cropper-window-container\">\n @if (!service.cropperRatio()) {\n <div (pointerdown)=\"handleSelector($event, 'right')\" class=\"ax-image-right-handle ax-handle-shape\"></div>\n <div (pointerdown)=\"handleSelector($event, 'top')\" class=\"ax-image-top-handle ax-handle-shape\"></div>\n <div (pointerdown)=\"handleSelector($event, 'left')\" class=\"ax-image-left-handle ax-handle-shape\"></div>\n <div (pointerdown)=\"handleSelector($event, 'bottom')\" class=\"ax-image-bottom-handle ax-handle-shape\"></div>\n }\n\n <div (pointerdown)=\"handleSelector($event, 'right-top')\" class=\"ax-image-right-top-handle ax-handle-shape\"></div>\n <div\n (pointerdown)=\"handleSelector($event, 'bottom-right')\"\n class=\"ax-image-bottom-right-handle ax-handle-shape\"\n ></div>\n <div (pointerdown)=\"handleSelector($event, 'bottom-left')\" class=\"ax-image-bottom-left-handle ax-handle-shape\"></div>\n <div (pointerdown)=\"handleSelector($event, 'left-top')\" class=\"ax-image-left-top-handle ax-handle-shape\"></div>\n\n @if (showGrid()) {\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n }\n</div>\n", styles: ["@layer properties;@layer components{ax-image-editor-cropper-window{position:absolute;top:calc(var(--spacing, .25rem) * 0);left:calc(var(--spacing, .25rem) * 0);touch-action:none;-webkit-user-select:none;user-select:none}ax-image-editor-cropper-window .ax-image-cropper-window-container{position:relative;display:grid;height:100%;cursor:move;grid-template-columns:repeat(3,minmax(0,1fr));outline-style:var(--tw-outline-style);outline-width:100vmax;outline-color:color-mix(in srgb,#000 70%,transparent);--tw-outline-style: solid;outline-style:solid}@supports (color: color-mix(in lab,red,red)){ax-image-editor-cropper-window .ax-image-cropper-window-container{outline-color:color-mix(in oklab,var(--color-black, #000) 70%,transparent)}}ax-image-editor-cropper-window .ax-image-cropper-window-container .ax-grid-item{border-style:var(--tw-border-style);border-width:1px;--tw-border-style: dashed;border-style:dashed;border-color:color-mix(in srgb,#fff 50%,transparent)}@supports (color: color-mix(in lab,red,red)){ax-image-editor-cropper-window .ax-image-cropper-window-container .ax-grid-item{border-color:color-mix(in oklab,var(--color-white, #fff) 50%,transparent)}}ax-image-editor-cropper-window .ax-handle-shape{position:absolute;height:calc(var(--spacing, .25rem) * 4);width:calc(var(--spacing, .25rem) * 4);border-style:var(--tw-border-style);border-width:1px;--tw-border-style: dashed;border-style:dashed;border-color:var(--color-white, #fff);background-color:var(--color-gray-500, oklch(55.1% .027 264.364));-webkit-user-select:none;user-select:none}ax-image-editor-cropper-window .ax-image-right-top-handle{top:calc(var(--spacing, .25rem) * -2);right:calc(var(--spacing, .25rem) * -2);cursor:ne-resize}ax-image-editor-cropper-window .ax-image-bottom-right-handle{right:calc(var(--spacing, .25rem) * -2);bottom:calc(var(--spacing, .25rem) * -2);cursor:se-resize}ax-image-editor-cropper-window .ax-image-right-handle{top:calc(50% - .5rem);right:calc(var(--spacing, .25rem) * -2);cursor:e-resize}ax-image-editor-cropper-window .ax-image-bottom-left-handle{bottom:calc(var(--spacing, .25rem) * -2);left:calc(var(--spacing, .25rem) * -2);cursor:sw-resize}ax-image-editor-cropper-window .ax-image-left-top-handle{top:calc(var(--spacing, .25rem) * -2);left:calc(var(--spacing, .25rem) * -2);cursor:nw-resize}ax-image-editor-cropper-window .ax-image-bottom-handle{right:calc(50% - .5rem);bottom:calc(var(--spacing, .25rem) * -2);cursor:s-resize}ax-image-editor-cropper-window .ax-image-left-handle{top:calc(50% - .5rem);left:calc(var(--spacing, .25rem) * -2);cursor:w-resize}ax-image-editor-cropper-window .ax-image-top-handle{top:calc(var(--spacing, .25rem) * -2);right:calc(50% - .5rem);cursor:n-resize}}@property --tw-outline-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-outline-style: solid;--tw-border-style: solid}}}\n/*! tailwindcss v4.1.16 | MIT License | https://tailwindcss.com */\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
357
342
  }
358
343
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXImageEditorCropperWindowComponent, decorators: [{
359
344
  type: Component,
360
- args: [{ selector: 'ax-image-editor-cropper-window', encapsulation: ViewEncapsulation.None, providers: [{ provide: AXComponent, useExisting: AXImageEditorCropperWindowComponent }], template: "<div [style.border]=\"!showGrid() ? '1px dashed white' : null\" #r class=\"ax-image-cropper-window-container\">\n @if (!service.cropperRatio()) {\n <div (pointerdown)=\"handleSelector($event, 'right')\" class=\"ax-image-right-handle ax-handle-shape\"></div>\n <div (pointerdown)=\"handleSelector($event, 'top')\" class=\"ax-image-top-handle ax-handle-shape\"></div>\n <div (pointerdown)=\"handleSelector($event, 'left')\" class=\"ax-image-left-handle ax-handle-shape\"></div>\n <div (pointerdown)=\"handleSelector($event, 'bottom')\" class=\"ax-image-bottom-handle ax-handle-shape\"></div>\n }\n\n <div (pointerdown)=\"handleSelector($event, 'right-top')\" class=\"ax-image-right-top-handle ax-handle-shape\"></div>\n <div\n (pointerdown)=\"handleSelector($event, 'bottom-right')\"\n class=\"ax-image-bottom-right-handle ax-handle-shape\"\n ></div>\n <div (pointerdown)=\"handleSelector($event, 'bottom-left')\" class=\"ax-image-bottom-left-handle ax-handle-shape\"></div>\n <div (pointerdown)=\"handleSelector($event, 'left-top')\" class=\"ax-image-left-top-handle ax-handle-shape\"></div>\n\n @if (showGrid()) {\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n }\n</div>\n", styles: ["@layer properties;@layer components{ax-image-editor-cropper-window{position:absolute;top:45%;left:45%;touch-action:none;-webkit-user-select:none;user-select:none}ax-image-editor-cropper-window .ax-image-cropper-window-container{position:relative;display:grid;height:100%;cursor:move;grid-template-columns:repeat(3,minmax(0,1fr));outline-style:var(--tw-outline-style);outline-width:100vmax;outline-color:color-mix(in srgb,#000 70%,transparent);--tw-outline-style: solid;outline-style:solid}@supports (color: color-mix(in lab,red,red)){ax-image-editor-cropper-window .ax-image-cropper-window-container{outline-color:color-mix(in oklab,var(--color-black, #000) 70%,transparent)}}ax-image-editor-cropper-window .ax-image-cropper-window-container .ax-grid-item{border-style:var(--tw-border-style);border-width:1px;--tw-border-style: dashed;border-style:dashed;border-color:color-mix(in srgb,#fff 50%,transparent)}@supports (color: color-mix(in lab,red,red)){ax-image-editor-cropper-window .ax-image-cropper-window-container .ax-grid-item{border-color:color-mix(in oklab,var(--color-white, #fff) 50%,transparent)}}ax-image-editor-cropper-window .ax-handle-shape{position:absolute;height:calc(var(--spacing, .25rem) * 4);width:calc(var(--spacing, .25rem) * 4);border-style:var(--tw-border-style);border-width:1px;--tw-border-style: dashed;border-style:dashed;border-color:var(--color-white, #fff);background-color:var(--color-gray-500, oklch(55.1% .027 264.364));-webkit-user-select:none;user-select:none}ax-image-editor-cropper-window .ax-image-right-top-handle{top:calc(var(--spacing, .25rem) * -2);right:calc(var(--spacing, .25rem) * -2);cursor:ne-resize}ax-image-editor-cropper-window .ax-image-bottom-right-handle{right:calc(var(--spacing, .25rem) * -2);bottom:calc(var(--spacing, .25rem) * -2);cursor:se-resize}ax-image-editor-cropper-window .ax-image-right-handle{top:calc(50% - .5rem);right:calc(var(--spacing, .25rem) * -2);cursor:e-resize}ax-image-editor-cropper-window .ax-image-bottom-left-handle{bottom:calc(var(--spacing, .25rem) * -2);left:calc(var(--spacing, .25rem) * -2);cursor:sw-resize}ax-image-editor-cropper-window .ax-image-left-top-handle{top:calc(var(--spacing, .25rem) * -2);left:calc(var(--spacing, .25rem) * -2);cursor:nw-resize}ax-image-editor-cropper-window .ax-image-bottom-handle{right:calc(50% - .5rem);bottom:calc(var(--spacing, .25rem) * -2);cursor:s-resize}ax-image-editor-cropper-window .ax-image-left-handle{top:calc(50% - .5rem);left:calc(var(--spacing, .25rem) * -2);cursor:w-resize}ax-image-editor-cropper-window .ax-image-top-handle{top:calc(var(--spacing, .25rem) * -2);right:calc(50% - .5rem);cursor:n-resize}}@property --tw-outline-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-outline-style: solid;--tw-border-style: solid}}}\n/*! tailwindcss v4.1.16 | MIT License | https://tailwindcss.com */\n"] }]
345
+ args: [{ selector: 'ax-image-editor-cropper-window', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, providers: [{ provide: AXComponent, useExisting: AXImageEditorCropperWindowComponent }], template: "<div [style.border]=\"!showGrid() ? '1px dashed white' : null\" #r class=\"ax-image-cropper-window-container\">\n @if (!service.cropperRatio()) {\n <div (pointerdown)=\"handleSelector($event, 'right')\" class=\"ax-image-right-handle ax-handle-shape\"></div>\n <div (pointerdown)=\"handleSelector($event, 'top')\" class=\"ax-image-top-handle ax-handle-shape\"></div>\n <div (pointerdown)=\"handleSelector($event, 'left')\" class=\"ax-image-left-handle ax-handle-shape\"></div>\n <div (pointerdown)=\"handleSelector($event, 'bottom')\" class=\"ax-image-bottom-handle ax-handle-shape\"></div>\n }\n\n <div (pointerdown)=\"handleSelector($event, 'right-top')\" class=\"ax-image-right-top-handle ax-handle-shape\"></div>\n <div\n (pointerdown)=\"handleSelector($event, 'bottom-right')\"\n class=\"ax-image-bottom-right-handle ax-handle-shape\"\n ></div>\n <div (pointerdown)=\"handleSelector($event, 'bottom-left')\" class=\"ax-image-bottom-left-handle ax-handle-shape\"></div>\n <div (pointerdown)=\"handleSelector($event, 'left-top')\" class=\"ax-image-left-top-handle ax-handle-shape\"></div>\n\n @if (showGrid()) {\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n <div class=\"ax-grid-item\"></div>\n }\n</div>\n", styles: ["@layer properties;@layer components{ax-image-editor-cropper-window{position:absolute;top:calc(var(--spacing, .25rem) * 0);left:calc(var(--spacing, .25rem) * 0);touch-action:none;-webkit-user-select:none;user-select:none}ax-image-editor-cropper-window .ax-image-cropper-window-container{position:relative;display:grid;height:100%;cursor:move;grid-template-columns:repeat(3,minmax(0,1fr));outline-style:var(--tw-outline-style);outline-width:100vmax;outline-color:color-mix(in srgb,#000 70%,transparent);--tw-outline-style: solid;outline-style:solid}@supports (color: color-mix(in lab,red,red)){ax-image-editor-cropper-window .ax-image-cropper-window-container{outline-color:color-mix(in oklab,var(--color-black, #000) 70%,transparent)}}ax-image-editor-cropper-window .ax-image-cropper-window-container .ax-grid-item{border-style:var(--tw-border-style);border-width:1px;--tw-border-style: dashed;border-style:dashed;border-color:color-mix(in srgb,#fff 50%,transparent)}@supports (color: color-mix(in lab,red,red)){ax-image-editor-cropper-window .ax-image-cropper-window-container .ax-grid-item{border-color:color-mix(in oklab,var(--color-white, #fff) 50%,transparent)}}ax-image-editor-cropper-window .ax-handle-shape{position:absolute;height:calc(var(--spacing, .25rem) * 4);width:calc(var(--spacing, .25rem) * 4);border-style:var(--tw-border-style);border-width:1px;--tw-border-style: dashed;border-style:dashed;border-color:var(--color-white, #fff);background-color:var(--color-gray-500, oklch(55.1% .027 264.364));-webkit-user-select:none;user-select:none}ax-image-editor-cropper-window .ax-image-right-top-handle{top:calc(var(--spacing, .25rem) * -2);right:calc(var(--spacing, .25rem) * -2);cursor:ne-resize}ax-image-editor-cropper-window .ax-image-bottom-right-handle{right:calc(var(--spacing, .25rem) * -2);bottom:calc(var(--spacing, .25rem) * -2);cursor:se-resize}ax-image-editor-cropper-window .ax-image-right-handle{top:calc(50% - .5rem);right:calc(var(--spacing, .25rem) * -2);cursor:e-resize}ax-image-editor-cropper-window .ax-image-bottom-left-handle{bottom:calc(var(--spacing, .25rem) * -2);left:calc(var(--spacing, .25rem) * -2);cursor:sw-resize}ax-image-editor-cropper-window .ax-image-left-top-handle{top:calc(var(--spacing, .25rem) * -2);left:calc(var(--spacing, .25rem) * -2);cursor:nw-resize}ax-image-editor-cropper-window .ax-image-bottom-handle{right:calc(50% - .5rem);bottom:calc(var(--spacing, .25rem) * -2);cursor:s-resize}ax-image-editor-cropper-window .ax-image-left-handle{top:calc(50% - .5rem);left:calc(var(--spacing, .25rem) * -2);cursor:w-resize}ax-image-editor-cropper-window .ax-image-top-handle{top:calc(var(--spacing, .25rem) * -2);right:calc(50% - .5rem);cursor:n-resize}}@property --tw-outline-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-outline-style: solid;--tw-border-style: solid}}}\n/*! tailwindcss v4.1.16 | MIT License | https://tailwindcss.com */\n"] }]
361
346
  }], propDecorators: { showGrid: [{ type: i0.Input, args: [{ isSignal: true, alias: "showGrid", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], cropperWindow: [{ type: i0.ViewChild, args: ['r', { isSignal: true }] }] } });
362
347
 
363
- class AXImageEditorViewComponent extends MXBaseComponent {
364
- /** @ignore */
365
- resizeWindow() {
366
- this.canvasResponsive(this.service.newImage);
367
- }
368
- /** @ignore */
369
- canvasResponsive(image) {
370
- const imageWidth = image.width;
371
- const imageHeight = image.height;
372
- this.originalWidth.set(imageWidth);
373
- this.originalHeight.set(imageHeight);
374
- if (imageWidth > imageHeight) {
375
- this.canvasElem().nativeElement.width = imageWidth;
376
- this.canvasElem().nativeElement.height = imageWidth;
377
- }
378
- else {
379
- this.canvasElem().nativeElement.width = imageHeight;
380
- this.canvasElem().nativeElement.height = imageHeight;
348
+ const IMAGE_EDITOR_MAX_BUFFER = 4096;
349
+ const IMAGE_EDITOR_EXPORT_QUALITY = 1;
350
+ function loadHtmlImage(src) {
351
+ return new Promise((resolve, reject) => {
352
+ const image = new Image();
353
+ if (!src.startsWith('data:') && !src.startsWith('blob:')) {
354
+ image.crossOrigin = 'anonymous';
381
355
  }
382
- requestAnimationFrame(() => {
383
- //recalculate width after canvas size change
384
- const recalculateWidth = this.canvasElem().nativeElement.getClientRects()[0]?.width;
385
- let scale;
386
- if (imageWidth > imageHeight) {
387
- scale = this.originalWidth() / recalculateWidth;
388
- }
389
- else {
390
- scale = this.originalHeight() / recalculateWidth;
391
- }
392
- this.scale.set(scale);
393
- this.canvasElem().nativeElement.width = recalculateWidth;
394
- this.canvasElem().nativeElement.height = recalculateWidth;
395
- if (imageWidth > imageHeight) {
396
- this.ctx().drawImage(image, 0, recalculateWidth / 2 - this.originalHeight() / scale / 2, recalculateWidth, this.originalHeight() / scale);
397
- }
398
- else {
399
- this.ctx().drawImage(image, recalculateWidth / 2 - this.originalWidth() / scale / 2, 0, this.originalWidth() / scale, recalculateWidth);
356
+ image.onload = () => resolve(image);
357
+ image.onerror = () => reject(new Error('Failed to load image'));
358
+ image.src = src;
359
+ });
360
+ }
361
+ function canvasToBlob(canvas, type, quality) {
362
+ return new Promise((resolve, reject) => {
363
+ canvas.toBlob((blob) => {
364
+ if (blob) {
365
+ resolve(blob);
366
+ return;
400
367
  }
401
- });
368
+ reject(new Error(`toBlob returned null for type ${type}`));
369
+ }, type, quality);
370
+ });
371
+ }
372
+ async function canvasToEditorBlobs(canvas) {
373
+ const jpeg = await canvasToBlob(canvas, 'image/jpeg', IMAGE_EDITOR_EXPORT_QUALITY);
374
+ const extra = await Promise.allSettled([
375
+ canvasToBlob(canvas, 'image/png'),
376
+ canvasToBlob(canvas, 'image/webp', IMAGE_EDITOR_EXPORT_QUALITY),
377
+ ]);
378
+ const blobs = [jpeg];
379
+ for (const result of extra) {
380
+ if (result.status === 'fulfilled') {
381
+ blobs.push(result.value);
382
+ }
402
383
  }
403
- /** @ignore */
384
+ return blobs;
385
+ }
386
+ function bufferSizeForImage(width, height, max = IMAGE_EDITOR_MAX_BUFFER) {
387
+ const scale = Math.min(1, max / Math.max(width, height, 1));
388
+ return {
389
+ width: Math.max(1, Math.round(width * scale)),
390
+ height: Math.max(1, Math.round(height * scale)),
391
+ };
392
+ }
393
+
394
+ class AXImageEditorViewComponent extends MXBaseComponent {
404
395
  constructor() {
405
396
  super();
406
397
  /**
@@ -442,44 +433,80 @@ class AXImageEditorViewComponent extends MXBaseComponent {
442
433
  ...(ngDevMode ? [{ debugName: "isDrawRectangleCreated" }] : /* istanbul ignore next */ []));
443
434
  /** @ignore */
444
435
  this.parent = inject(AXImageEditorContainerComponent);
445
- /** @ignore */
446
- this.renderer = inject(Renderer2);
436
+ this.destroyRef = inject(DestroyRef);
437
+ this.host = inject((ElementRef));
447
438
  /** @ignore */
448
439
  this.imageEditorHistory = signal([], /* @ts-ignore */
449
440
  ...(ngDevMode ? [{ debugName: "imageEditorHistory" }] : /* istanbul ignore next */ []));
450
441
  /** @ignore */
451
- this.imageEditorHistoryPointer = linkedSignal(() => this.imageEditorHistory().length - 1, /* @ts-ignore */
442
+ this.imageEditorHistoryPointer = signal(-1, /* @ts-ignore */
452
443
  ...(ngDevMode ? [{ debugName: "imageEditorHistoryPointer" }] : /* istanbul ignore next */ []));
453
- this.originalWidth = signal(0, /* @ts-ignore */
454
- ...(ngDevMode ? [{ debugName: "originalWidth" }] : /* istanbul ignore next */ []));
455
- this.originalHeight = signal(0, /* @ts-ignore */
456
- ...(ngDevMode ? [{ debugName: "originalHeight" }] : /* istanbul ignore next */ []));
457
- this.scale = signal(0, /* @ts-ignore */
458
- ...(ngDevMode ? [{ debugName: "scale" }] : /* istanbul ignore next */ []));
444
+ this.canvasReady = signal(false, /* @ts-ignore */
445
+ ...(ngDevMode ? [{ debugName: "canvasReady" }] : /* istanbul ignore next */ []));
446
+ this.drawing = false;
447
+ this.didDraw = false;
448
+ this.highlightSnapshot = null;
449
+ this.highlightPoints = [];
450
+ this.persistGeneration = 0;
451
+ this.sourceToken = 0;
459
452
  afterNextRender(() => {
460
- this.resizeEventListener = this.renderer.listen(window, 'resize', this.resizeWindow.bind(this));
461
- //set canvas options
462
- this.ctx.set(this.canvasElem().nativeElement.getContext('2d', { willReadFrequently: true }));
463
- this.ctx().lineJoin = 'round';
464
- this.ctx().lineCap = 'round';
453
+ const canvas = this.canvasElem()?.nativeElement;
454
+ if (!canvas) {
455
+ return;
456
+ }
457
+ const ctx = canvas.getContext('2d', { willReadFrequently: true });
458
+ if (!ctx) {
459
+ return;
460
+ }
461
+ ctx.lineJoin = 'round';
462
+ ctx.lineCap = 'round';
463
+ this.ctx.set(ctx);
464
+ this.canvasReady.set(true);
465
+ const onResize = () => this.fitCanvasDisplay();
466
+ window.addEventListener('resize', onResize);
467
+ this.destroyRef.onDestroy(() => window.removeEventListener('resize', onResize));
465
468
  });
466
469
  effect(() => {
467
- this.setInitialImage(this.src());
470
+ const src = this.src();
471
+ const ready = this.canvasReady();
472
+ if (!ready) {
473
+ return;
474
+ }
475
+ untracked(() => void this.loadSource(src));
468
476
  });
469
477
  effect(() => {
470
478
  if (!this.service.undoPressed()) {
471
- this.imageChanged();
479
+ return;
472
480
  }
481
+ untracked(() => {
482
+ this.applyUndo();
483
+ this.service.undoPressed.set(false);
484
+ });
473
485
  });
474
486
  effect(() => {
475
487
  if (!this.service.redoPressed()) {
476
- this.imageChanged();
488
+ return;
489
+ }
490
+ untracked(() => {
491
+ this.applyRedo();
492
+ this.service.redoPressed.set(false);
493
+ });
494
+ });
495
+ effect(() => {
496
+ const rotate = this.service.rotate();
497
+ const ctx = this.ctx();
498
+ if (!rotate.userInteract || !ctx) {
499
+ return;
477
500
  }
501
+ untracked(() => void this.rotateImage());
478
502
  });
479
503
  effect(() => {
480
- if (!this.service.rotate().userInteract || !this.ctx())
504
+ if (!this.service.isImageLoad()) {
481
505
  return;
482
- this.rotateImage();
506
+ }
507
+ untracked(() => {
508
+ requestAnimationFrame(() => this.fitCanvasDisplay());
509
+ });
483
510
  });
484
511
  effect(() => {
485
512
  const size = this.cropperSize();
@@ -489,167 +516,351 @@ class AXImageEditorViewComponent extends MXBaseComponent {
489
516
  });
490
517
  }
491
518
  /** @ignore */
492
- ngOnDestroy() {
493
- if (this.resizeEventListener) {
494
- this.resizeEventListener();
495
- }
496
- if (this.imageLoadEventListener) {
497
- this.imageLoadEventListener();
498
- }
519
+ resizeWindow() {
520
+ this.fitCanvasDisplay();
499
521
  }
500
- setInitialImage(src, replaceHistory = true) {
501
- this.service.initialImage = new Image();
502
- this.service.initialImage.src = src;
503
- this.service.initialImage.crossOrigin = 'anonymous';
504
- this.imageLoadEventListener = this.renderer.listen(this.service.initialImage, 'load', () => {
505
- this.service.newImage = this.service.initialImage;
506
- this.service.isImageLoad.set(true);
507
- this.canvasResponsive(this.service.initialImage);
508
- if (replaceHistory) {
509
- this.imageEditorHistory.set([this.service.initialImage]);
522
+ /** @ignore */
523
+ canvasResponsive(image) {
524
+ this.paintImage(image);
525
+ }
526
+ async loadSource(src) {
527
+ const token = ++this.sourceToken;
528
+ this.service.loadError.set(false);
529
+ this.service.isImageLoad.set(false);
530
+ this.service.imageBlob.set([]);
531
+ this.service.newImage = null;
532
+ this.service.initialImage = null;
533
+ if (!src) {
534
+ return;
535
+ }
536
+ try {
537
+ const image = await loadHtmlImage(src);
538
+ if (token !== this.sourceToken) {
539
+ return;
510
540
  }
511
- else {
512
- this.imageEditorHistory.update((prev) => [...prev, this.service.initialImage]);
541
+ this.service.initialImage = image;
542
+ this.service.newImage = image;
543
+ this.replaceHistory(image);
544
+ this.paintImage(image);
545
+ this.service.isImageLoad.set(true);
546
+ }
547
+ catch {
548
+ if (token !== this.sourceToken) {
549
+ return;
513
550
  }
514
- });
551
+ this.service.loadError.set(true);
552
+ this.service.isImageLoad.set(false);
553
+ }
515
554
  }
516
- /** @ignore */
517
- imageChanged() {
518
- if (this.service.undoPressed() && this.imageEditorHistoryPointer() >= 1) {
519
- this.imageEditorHistoryPointer.update((prev) => prev - 1);
520
- this.service.newImage = this.imageEditorHistory()[this.imageEditorHistoryPointer()];
521
- this.canvasResponsive(this.imageEditorHistory()[this.imageEditorHistoryPointer()]);
555
+ paintImage(image) {
556
+ const canvas = this.canvasElem()?.nativeElement;
557
+ const ctx = this.ctx();
558
+ if (!canvas || !ctx || !image) {
559
+ return;
522
560
  }
523
- if (this.service.redoPressed() && this.imageEditorHistoryPointer() < this.imageEditorHistory().length - 1) {
524
- this.imageEditorHistoryPointer.update((prev) => prev + 1);
525
- this.service.newImage = this.imageEditorHistory()[this.imageEditorHistoryPointer()];
526
- this.canvasResponsive(this.imageEditorHistory()[this.imageEditorHistoryPointer()]);
561
+ const buffer = bufferSizeForImage(image.naturalWidth || image.width, image.naturalHeight || image.height);
562
+ canvas.width = buffer.width;
563
+ canvas.height = buffer.height;
564
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
565
+ ctx.globalAlpha = 1;
566
+ ctx.globalCompositeOperation = 'source-over';
567
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
568
+ ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
569
+ this.fitCanvasDisplay();
570
+ }
571
+ fitCanvasDisplay() {
572
+ const canvas = this.canvasElem()?.nativeElement;
573
+ if (!canvas?.width || !canvas.height) {
574
+ return;
527
575
  }
528
- this.service.undoPressed.set(false);
529
- this.service.redoPressed.set(false);
576
+ const parent = this.host.nativeElement;
577
+ const maxW = Math.max(parent?.clientWidth || 0, 240);
578
+ const maxH = Math.min(window.innerHeight * 0.55, 560);
579
+ const scale = Math.min(maxW / canvas.width, maxH / canvas.height, 1);
580
+ canvas.style.width = `${Math.max(1, Math.round(canvas.width * scale))}px`;
581
+ canvas.style.height = `${Math.max(1, Math.round(canvas.height * scale))}px`;
582
+ }
583
+ replaceHistory(image) {
584
+ this.imageEditorHistory.set([image]);
585
+ this.imageEditorHistoryPointer.set(0);
586
+ this.syncHistoryFlags();
587
+ }
588
+ pushHistory(image) {
589
+ const pointer = this.imageEditorHistoryPointer();
590
+ const next = this.imageEditorHistory().slice(0, pointer + 1);
591
+ next.push(image);
592
+ this.imageEditorHistory.set(next);
593
+ this.imageEditorHistoryPointer.set(next.length - 1);
594
+ this.syncHistoryFlags();
595
+ }
596
+ syncHistoryFlags() {
597
+ const pointer = this.imageEditorHistoryPointer();
598
+ const length = this.imageEditorHistory().length;
599
+ this.service.canUndo.set(pointer > 0);
600
+ this.service.canRedo.set(pointer >= 0 && pointer < length - 1);
601
+ }
602
+ applyUndo() {
603
+ const pointer = this.imageEditorHistoryPointer();
604
+ if (pointer < 1) {
605
+ this.syncHistoryFlags();
606
+ return;
607
+ }
608
+ const nextPointer = pointer - 1;
609
+ const image = this.imageEditorHistory()[nextPointer];
610
+ this.imageEditorHistoryPointer.set(nextPointer);
611
+ this.service.newImage = image;
612
+ this.paintImage(image);
613
+ this.syncHistoryFlags();
614
+ void this.persistCanvas({ skipHistory: true });
615
+ }
616
+ applyRedo() {
617
+ const history = this.imageEditorHistory();
618
+ const pointer = this.imageEditorHistoryPointer();
619
+ if (pointer >= history.length - 1) {
620
+ this.syncHistoryFlags();
621
+ return;
622
+ }
623
+ const nextPointer = pointer + 1;
624
+ const image = history[nextPointer];
625
+ this.imageEditorHistoryPointer.set(nextPointer);
626
+ this.service.newImage = image;
627
+ this.paintImage(image);
628
+ this.syncHistoryFlags();
629
+ void this.persistCanvas({ skipHistory: true });
630
+ }
631
+ canvasPoint(event) {
632
+ const canvas = this.canvasElem().nativeElement;
633
+ const rect = canvas.getBoundingClientRect();
634
+ const width = rect.width || 1;
635
+ const height = rect.height || 1;
636
+ return {
637
+ x: ((event.clientX - rect.left) / width) * canvas.width,
638
+ y: ((event.clientY - rect.top) / height) * canvas.height,
639
+ };
640
+ }
641
+ isDrawTool() {
642
+ const tool = this.service.penType();
643
+ return tool === 'pen' || tool === 'highlight';
530
644
  }
531
645
  /** @ignore */
532
646
  penConfigHandler(penType) {
647
+ const ctx = this.ctx();
648
+ if (!ctx) {
649
+ return;
650
+ }
651
+ ctx.lineJoin = 'round';
652
+ ctx.lineCap = 'round';
533
653
  switch (penType) {
534
654
  case 'pen':
535
- this.ctx().globalAlpha = 1;
536
- this.ctx().strokeStyle = this.service.penColor();
537
- this.ctx().lineWidth = this.service.penWidth();
655
+ ctx.globalAlpha = 1;
656
+ ctx.globalCompositeOperation = 'source-over';
657
+ ctx.strokeStyle = this.service.penColor();
658
+ ctx.lineWidth = this.service.penWidth();
538
659
  break;
539
660
  case 'highlight':
540
- this.ctx().globalAlpha = 0.008;
541
- this.ctx().strokeStyle = this.service.highlightColor();
542
- this.ctx().lineWidth = this.service.highlightWidth();
661
+ ctx.globalAlpha = 0.4;
662
+ ctx.globalCompositeOperation = 'multiply';
663
+ ctx.strokeStyle = this.service.highlightColor();
664
+ ctx.lineWidth = this.service.highlightWidth();
543
665
  break;
544
666
  }
545
667
  }
668
+ strokeHighlight(points) {
669
+ const ctx = this.ctx();
670
+ if (!ctx || points.length === 0) {
671
+ return;
672
+ }
673
+ this.penConfigHandler('highlight');
674
+ ctx.beginPath();
675
+ ctx.moveTo(points[0].x, points[0].y);
676
+ for (let i = 1; i < points.length; i++) {
677
+ ctx.lineTo(points[i].x, points[i].y);
678
+ }
679
+ if (points.length === 1) {
680
+ ctx.lineTo(points[0].x, points[0].y);
681
+ }
682
+ ctx.stroke();
683
+ }
546
684
  /** @ignore */
547
- mouseDownHandler(e) {
548
- this.isMouseDown.set(true);
549
- if (!this.service.penType())
685
+ mouseDownHandler(event) {
686
+ if (!this.isDrawTool() || this.parent.disabled) {
687
+ return;
688
+ }
689
+ const ctx = this.ctx();
690
+ const canvas = this.canvasElem()?.nativeElement;
691
+ const penType = this.service.penType();
692
+ if (!ctx || !canvas || !penType) {
550
693
  return;
551
- this.penConfigHandler(this.service.penType());
552
- this.ctx().beginPath();
553
- this.ctx().moveTo(e.offsetX, e.offsetY);
554
- this.ctx().lineTo(e.offsetX, e.offsetY);
555
- this.ctx().stroke();
694
+ }
695
+ event.preventDefault();
696
+ canvas.setPointerCapture(event.pointerId);
697
+ this.isMouseDown.set(true);
698
+ this.drawing = true;
699
+ this.didDraw = false;
700
+ const point = this.canvasPoint(event);
701
+ this.penConfigHandler(penType);
702
+ if (penType === 'highlight') {
703
+ this.highlightSnapshot = ctx.getImageData(0, 0, canvas.width, canvas.height);
704
+ this.highlightPoints = [point];
705
+ this.strokeHighlight(this.highlightPoints);
706
+ }
707
+ else {
708
+ ctx.beginPath();
709
+ ctx.moveTo(point.x, point.y);
710
+ ctx.lineTo(point.x, point.y);
711
+ ctx.stroke();
712
+ }
713
+ this.didDraw = true;
556
714
  }
557
715
  /** @ignore */
558
- mouseMoveHandler(e) {
559
- if (!this.isMouseDown())
716
+ mouseMoveHandler(event) {
717
+ if (!this.drawing || !this.isDrawTool()) {
560
718
  return;
561
- if (!this.service.penType())
719
+ }
720
+ const ctx = this.ctx();
721
+ const canvas = this.canvasElem()?.nativeElement;
722
+ const penType = this.service.penType();
723
+ if (!ctx || !canvas || !penType) {
562
724
  return;
563
- this.ctx().lineTo(e.offsetX, e.offsetY);
564
- this.ctx().stroke();
725
+ }
726
+ const point = this.canvasPoint(event);
727
+ if (penType === 'highlight' && this.highlightSnapshot) {
728
+ ctx.putImageData(this.highlightSnapshot, 0, 0);
729
+ this.highlightPoints.push(point);
730
+ this.strokeHighlight(this.highlightPoints);
731
+ }
732
+ else {
733
+ ctx.lineTo(point.x, point.y);
734
+ ctx.stroke();
735
+ }
736
+ this.didDraw = true;
565
737
  }
566
738
  /** @ignore */
567
- mouseUpHandler() {
739
+ mouseUpHandler(event) {
740
+ const canvas = this.canvasElem()?.nativeElement;
741
+ if (event && canvas?.hasPointerCapture(event.pointerId)) {
742
+ canvas.releasePointerCapture(event.pointerId);
743
+ }
744
+ const wasDrawing = this.drawing && this.didDraw;
745
+ this.drawing = false;
746
+ this.didDraw = false;
568
747
  this.isMouseDown.set(false);
569
- this.ctx().closePath();
570
- this.saveImageChange();
748
+ this.highlightSnapshot = null;
749
+ this.highlightPoints = [];
750
+ const ctx = this.ctx();
751
+ if (ctx) {
752
+ ctx.closePath();
753
+ ctx.globalAlpha = 1;
754
+ ctx.globalCompositeOperation = 'source-over';
755
+ }
756
+ if (wasDrawing) {
757
+ void this.persistCanvas();
758
+ }
571
759
  }
572
- /** @ignore */
760
+ /** Applies the current cropper selection. */
573
761
  async crop() {
574
762
  const cropper = this.cropperWindow();
575
- if (this.service.activeToolState() !== 'crop' || !cropper) {
763
+ const ctx = this.ctx();
764
+ const canvas = this.canvasElem()?.nativeElement;
765
+ if (this.service.activeToolState() !== 'crop' || !cropper || !ctx || !canvas) {
576
766
  return;
577
767
  }
578
768
  const cropRect = cropper.getCropperArea();
579
- const canvas = this.canvasElem().nativeElement;
580
769
  const canvasRect = canvas.getBoundingClientRect();
581
- const scaleX = canvas.width / canvasRect.width;
582
- const scaleY = canvas.height / canvasRect.height;
583
- const dx = (cropRect.x - canvasRect.x) * scaleX;
584
- const dy = (cropRect.y - canvasRect.y) * scaleY;
585
- const cropWidth = cropRect.width * scaleX;
586
- const cropHeight = cropRect.height * scaleY;
587
- const imageData = this.ctx().getImageData(Math.round(dx), Math.round(dy), Math.round(cropWidth), Math.round(cropHeight));
588
- canvas.width = Math.round(cropWidth);
589
- canvas.height = Math.round(cropHeight);
590
- this.ctx().putImageData(imageData, 0, 0);
591
- const cropImage = this.canvasElem().nativeElement.toDataURL('image/jpeg', 1);
592
- await this.saveImageChange();
593
- this.setInitialImage(cropImage, false);
770
+ const scaleX = canvas.width / (canvasRect.width || 1);
771
+ const scaleY = canvas.height / (canvasRect.height || 1);
772
+ const dx = Math.max(0, Math.round((cropRect.x - canvasRect.x) * scaleX));
773
+ const dy = Math.max(0, Math.round((cropRect.y - canvasRect.y) * scaleY));
774
+ const cropWidth = Math.max(1, Math.round(cropRect.width * scaleX));
775
+ const cropHeight = Math.max(1, Math.round(cropRect.height * scaleY));
776
+ const width = Math.min(cropWidth, canvas.width - dx);
777
+ const height = Math.min(cropHeight, canvas.height - dy);
778
+ const imageData = ctx.getImageData(dx, dy, width, height);
779
+ canvas.width = width;
780
+ canvas.height = height;
781
+ ctx.putImageData(imageData, 0, 0);
782
+ this.fitCanvasDisplay();
594
783
  this.service.activeToolState.set(null);
784
+ this.service.penType.set(null);
785
+ await this.persistCanvas();
595
786
  }
596
- /** @ignore */
597
- rotateImage() {
598
- this.ctx().globalAlpha = 1;
599
- this.ctx().save();
600
- this.ctx().clearRect(0, 0, this.canvasElem().nativeElement.width, this.canvasElem().nativeElement.height);
601
- // Calculate scale factor to fit rotated image within canvas
602
- const rotationAngle = (this.service.rotate().state * Math.PI) / 180;
603
- const cos = Math.abs(Math.cos(rotationAngle));
604
- const sin = Math.abs(Math.sin(rotationAngle));
605
- const rotatedWidth = this.service.newImage.width * cos + this.service.newImage.height * sin;
606
- const rotatedHeight = this.service.newImage.width * sin + this.service.newImage.height * cos;
607
- const scaleX = this.canvasElem().nativeElement.width / rotatedWidth;
608
- const scaleY = this.canvasElem().nativeElement.height / rotatedHeight;
609
- const scale = Math.min(scaleX, scaleY);
610
- this.ctx().translate(this.canvasElem().nativeElement.width / 2, this.canvasElem().nativeElement.height / 2);
611
- this.ctx().rotate(rotationAngle);
612
- this.ctx().scale(scale, scale);
613
- this.ctx().drawImage(this.service.newImage, -this.service.newImage.width / 2, -this.service.newImage.height / 2);
614
- this.ctx().restore();
615
- this.saveImageChange();
616
- this.service.rotate.update((prev) => {
617
- return { state: prev.state, userInteract: false };
618
- });
787
+ /** Encodes the current canvas into JPEG, PNG, and WebP blobs. */
788
+ async commitChanges() {
789
+ await this.persistCanvas();
790
+ return this.service.imageBlob();
619
791
  }
620
792
  /** @ignore */
621
- async saveImageChange() {
622
- const canvas = this.canvasElem().nativeElement;
623
- const newImage = new Image();
624
- newImage.crossOrigin = 'anonymous';
625
- newImage.src = canvas.toDataURL('image/jpeg', 1);
626
- this.service.newImage = newImage;
627
- this.imageEditorHistory.update((prev) => [...prev, newImage]);
628
- const canvasToBlob = (type, quality) => {
629
- return new Promise((resolve, reject) => {
630
- canvas.toBlob((blob) => {
631
- if (blob) {
632
- resolve(blob);
633
- }
634
- else {
635
- reject(new Error(`toBlob returned null for type ${type}`));
636
- }
637
- }, type, quality);
638
- });
639
- };
640
- const blobs = await Promise.all([
641
- canvasToBlob('image/jpeg', 1),
642
- canvasToBlob('image/png', 1),
643
- canvasToBlob('image/webp', 1),
644
- ]);
645
- this.service.imageBlob.set(blobs);
793
+ async rotateImage() {
794
+ const canvas = this.canvasElem()?.nativeElement;
795
+ const ctx = this.ctx();
796
+ if (!canvas || !ctx || !canvas.width || !canvas.height) {
797
+ this.service.rotate.update((prev) => ({ state: prev.state, userInteract: false }));
798
+ return;
799
+ }
800
+ const source = document.createElement('canvas');
801
+ source.width = canvas.width;
802
+ source.height = canvas.height;
803
+ const sourceCtx = source.getContext('2d');
804
+ if (!sourceCtx) {
805
+ this.service.rotate.update((prev) => ({ state: prev.state, userInteract: false }));
806
+ return;
807
+ }
808
+ sourceCtx.drawImage(canvas, 0, 0);
809
+ const degrees = this.service.rotate().state;
810
+ const radians = (degrees * Math.PI) / 180;
811
+ const cos = Math.abs(Math.cos(radians));
812
+ const sin = Math.abs(Math.sin(radians));
813
+ const rotatedWidth = Math.max(1, Math.round(source.width * cos + source.height * sin));
814
+ const rotatedHeight = Math.max(1, Math.round(source.width * sin + source.height * cos));
815
+ canvas.width = rotatedWidth;
816
+ canvas.height = rotatedHeight;
817
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
818
+ ctx.globalAlpha = 1;
819
+ ctx.globalCompositeOperation = 'source-over';
820
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
821
+ ctx.save();
822
+ ctx.translate(canvas.width / 2, canvas.height / 2);
823
+ ctx.rotate(radians);
824
+ ctx.drawImage(source, -source.width / 2, -source.height / 2);
825
+ ctx.restore();
826
+ this.fitCanvasDisplay();
827
+ await this.persistCanvas();
828
+ this.service.rotate.update((prev) => ({ state: prev.state, userInteract: false }));
829
+ }
830
+ async persistCanvas(options) {
831
+ const canvas = this.canvasElem()?.nativeElement;
832
+ if (!canvas?.width || !canvas.height) {
833
+ return;
834
+ }
835
+ const generation = ++this.persistGeneration;
836
+ try {
837
+ const blobs = await canvasToEditorBlobs(canvas);
838
+ if (generation !== this.persistGeneration) {
839
+ return;
840
+ }
841
+ this.service.imageBlob.set(blobs);
842
+ const dataUrl = canvas.toDataURL('image/jpeg', 1);
843
+ const image = await loadHtmlImage(dataUrl);
844
+ if (generation !== this.persistGeneration) {
845
+ return;
846
+ }
847
+ this.service.newImage = image;
848
+ if (!options?.skipHistory) {
849
+ this.pushHistory(image);
850
+ }
851
+ }
852
+ catch {
853
+ if (generation !== this.persistGeneration) {
854
+ return;
855
+ }
856
+ }
646
857
  }
647
858
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXImageEditorViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
648
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXImageEditorViewComponent, isStandalone: true, selector: "ax-image-editor-view", inputs: { showGrid: { classPropertyName: "showGrid", publicName: "showGrid", isSignal: true, isRequired: false, transformFunction: null }, cropperSize: { classPropertyName: "cropperSize", publicName: "cropperSize", isSignal: true, isRequired: false, transformFunction: null }, src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: AXComponent, useExisting: AXImageEditorViewComponent }], viewQueries: [{ propertyName: "canvasElem", first: true, predicate: ["canvasElem"], descendants: true, isSignal: true }, { propertyName: "cropperWindow", first: true, predicate: ["f"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<canvas\n tabindex=\"1\"\n (pointerdown)=\"mouseDownHandler($event)\"\n (pointerup)=\"mouseUpHandler()\"\n (pointermove)=\"mouseMoveHandler($event)\"\n class=\"ax-canvas-element\"\n #canvasElem\n></canvas>\n\n@if (this.service.activeToolState() === 'crop') {\n <ax-image-editor-cropper-window [showGrid]=\"showGrid()\" #f></ax-image-editor-cropper-window>\n}\n", styles: ["@layer components{ax-image-editor-view{position:relative;display:flex;width:100%;touch-action:none;align-items:center;justify-content:center;overflow:hidden;background-color:var(--color-white, #fff)}ax-image-editor-view .ax-canvas-element{cursor:crosshair;background-color:var(--color-black, #000)}ax-image-editor-view .ax-crop-save{position:absolute;top:calc(var(--spacing, .25rem) * 0);right:calc(var(--spacing, .25rem) * 0);margin:calc(var(--spacing, .25rem) * 4)}}\n/*! tailwindcss v4.1.16 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "component", type: AXImageEditorCropperWindowComponent, selector: "ax-image-editor-cropper-window", inputs: ["showGrid", "size"] }], encapsulation: i0.ViewEncapsulation.None }); }
859
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXImageEditorViewComponent, isStandalone: true, selector: "ax-image-editor-view", inputs: { showGrid: { classPropertyName: "showGrid", publicName: "showGrid", isSignal: true, isRequired: false, transformFunction: null }, cropperSize: { classPropertyName: "cropperSize", publicName: "cropperSize", isSignal: true, isRequired: false, transformFunction: null }, src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: AXComponent, useExisting: AXImageEditorViewComponent }], viewQueries: [{ propertyName: "canvasElem", first: true, predicate: ["canvasElem"], descendants: true, isSignal: true }, { propertyName: "cropperWindow", first: true, predicate: ["f"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"ax-image-editor-stage\">\n <canvas\n tabindex=\"0\"\n (pointerdown)=\"mouseDownHandler($event)\"\n (pointerup)=\"mouseUpHandler($event)\"\n (pointercancel)=\"mouseUpHandler($event)\"\n (pointermove)=\"mouseMoveHandler($event)\"\n class=\"ax-canvas-element\"\n #canvasElem\n ></canvas>\n\n @if (service.activeToolState() === 'crop') {\n <ax-image-editor-cropper-window [showGrid]=\"showGrid()\" #f></ax-image-editor-cropper-window>\n }\n</div>\n", styles: ["@layer components{ax-image-editor-view{position:relative;display:flex;width:100%;touch-action:none;align-items:center;justify-content:center;overflow:hidden;background-color:var(--color-white, #fff)}ax-image-editor-view .ax-image-editor-stage{position:relative;display:inline-flex;max-height:100%;max-width:100%;align-items:center;justify-content:center}ax-image-editor-view .ax-canvas-element{max-height:100%;max-width:100%;cursor:crosshair;background-color:var(--color-black, #000);touch-action:none}ax-image-editor-view .ax-crop-save{position:absolute;top:calc(var(--spacing, .25rem) * 0);right:calc(var(--spacing, .25rem) * 0);margin:calc(var(--spacing, .25rem) * 4)}}\n/*! tailwindcss v4.1.16 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "component", type: AXImageEditorCropperWindowComponent, selector: "ax-image-editor-cropper-window", inputs: ["showGrid", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
649
860
  }
650
861
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXImageEditorViewComponent, decorators: [{
651
862
  type: Component,
652
- args: [{ selector: 'ax-image-editor-view', encapsulation: ViewEncapsulation.None, imports: [AXImageEditorCropperWindowComponent], providers: [{ provide: AXComponent, useExisting: AXImageEditorViewComponent }], template: "<canvas\n tabindex=\"1\"\n (pointerdown)=\"mouseDownHandler($event)\"\n (pointerup)=\"mouseUpHandler()\"\n (pointermove)=\"mouseMoveHandler($event)\"\n class=\"ax-canvas-element\"\n #canvasElem\n></canvas>\n\n@if (this.service.activeToolState() === 'crop') {\n <ax-image-editor-cropper-window [showGrid]=\"showGrid()\" #f></ax-image-editor-cropper-window>\n}\n", styles: ["@layer components{ax-image-editor-view{position:relative;display:flex;width:100%;touch-action:none;align-items:center;justify-content:center;overflow:hidden;background-color:var(--color-white, #fff)}ax-image-editor-view .ax-canvas-element{cursor:crosshair;background-color:var(--color-black, #000)}ax-image-editor-view .ax-crop-save{position:absolute;top:calc(var(--spacing, .25rem) * 0);right:calc(var(--spacing, .25rem) * 0);margin:calc(var(--spacing, .25rem) * 4)}}\n/*! tailwindcss v4.1.16 | MIT License | https://tailwindcss.com */\n"] }]
863
+ args: [{ selector: 'ax-image-editor-view', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [AXImageEditorCropperWindowComponent], providers: [{ provide: AXComponent, useExisting: AXImageEditorViewComponent }], template: "<div class=\"ax-image-editor-stage\">\n <canvas\n tabindex=\"0\"\n (pointerdown)=\"mouseDownHandler($event)\"\n (pointerup)=\"mouseUpHandler($event)\"\n (pointercancel)=\"mouseUpHandler($event)\"\n (pointermove)=\"mouseMoveHandler($event)\"\n class=\"ax-canvas-element\"\n #canvasElem\n ></canvas>\n\n @if (service.activeToolState() === 'crop') {\n <ax-image-editor-cropper-window [showGrid]=\"showGrid()\" #f></ax-image-editor-cropper-window>\n }\n</div>\n", styles: ["@layer components{ax-image-editor-view{position:relative;display:flex;width:100%;touch-action:none;align-items:center;justify-content:center;overflow:hidden;background-color:var(--color-white, #fff)}ax-image-editor-view .ax-image-editor-stage{position:relative;display:inline-flex;max-height:100%;max-width:100%;align-items:center;justify-content:center}ax-image-editor-view .ax-canvas-element{max-height:100%;max-width:100%;cursor:crosshair;background-color:var(--color-black, #000);touch-action:none}ax-image-editor-view .ax-crop-save{position:absolute;top:calc(var(--spacing, .25rem) * 0);right:calc(var(--spacing, .25rem) * 0);margin:calc(var(--spacing, .25rem) * 4)}}\n/*! tailwindcss v4.1.16 | MIT License | https://tailwindcss.com */\n"] }]
653
864
  }], ctorParameters: () => [], propDecorators: { showGrid: [{ type: i0.Input, args: [{ isSignal: true, alias: "showGrid", required: false }] }], cropperSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "cropperSize", required: false }] }], src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], canvasElem: [{ type: i0.ViewChild, args: ['canvasElem', { isSignal: true }] }], cropperWindow: [{ type: i0.ViewChild, args: ['f', { isSignal: true }] }] } });
654
865
 
655
866
  /**
@@ -665,18 +876,18 @@ class AXImageEditorContainerComponent extends classes((MXInputBaseValueComponent
665
876
  ...(ngDevMode ? [{ debugName: "imageEditorContainer" }] : /* istanbul ignore next */ []));
666
877
  this.view = contentChild(AXImageEditorViewComponent, /* @ts-ignore */
667
878
  ...(ngDevMode ? [{ debugName: "view" }] : /* istanbul ignore next */ []));
668
- this.#eff = effect(() => {
669
- void this.service.imageBlob();
670
- if (this.service.imageBlob().length) {
671
- this.commitValue(this.service.imageBlob(), true);
879
+ this.#syncValue = effect(() => {
880
+ const blobs = this.service.imageBlob();
881
+ if (blobs.length) {
882
+ this.commitValue(blobs, true);
672
883
  }
673
884
  else {
674
885
  this.commitValue(null);
675
886
  }
676
887
  }, /* @ts-ignore */
677
- ...(ngDevMode ? [{ debugName: "#eff" }] : /* istanbul ignore next */ []));
888
+ ...(ngDevMode ? [{ debugName: "#syncValue" }] : /* istanbul ignore next */ []));
678
889
  }
679
- #eff;
890
+ #syncValue;
680
891
  /** Applies the current cropper selection. */
681
892
  async crop() {
682
893
  const oldValue = this.value;
@@ -686,16 +897,19 @@ class AXImageEditorContainerComponent extends classes((MXInputBaseValueComponent
686
897
  this.commitValue(blobs, true);
687
898
  }
688
899
  }
689
- get __hostClass() {
690
- if (this.disabled)
691
- return 'ax-state-disabled';
692
- return '';
693
- }
694
- get __hostName() {
695
- return this.name;
900
+ /** Encodes the current canvas into JPEG, PNG, and WebP blobs. */
901
+ async commitChanges() {
902
+ const view = this.view();
903
+ if (!view) {
904
+ return this.service.imageBlob();
905
+ }
906
+ if (this.service.activeToolState() === 'crop') {
907
+ await view.crop();
908
+ }
909
+ return view.commitChanges();
696
910
  }
697
911
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXImageEditorContainerComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
698
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXImageEditorContainerComponent, isStandalone: true, selector: "ax-image-editor-container", inputs: { look: "look", disabled: "disabled" }, outputs: { onValueChanged: "onValueChanged" }, host: { properties: { "class": "this.__hostClass", "attr.name": "this.__hostName" } }, providers: [
912
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXImageEditorContainerComponent, isStandalone: true, selector: "ax-image-editor-container", inputs: { look: "look", disabled: "disabled" }, outputs: { onValueChanged: "onValueChanged" }, host: { properties: { "class.ax-state-disabled": "disabled", "attr.name": "name" } }, providers: [
699
913
  AXImageEditorService,
700
914
  { provide: AXComponent, useExisting: AXImageEditorContainerComponent },
701
915
  { provide: AXFocusableComponent, useExisting: AXImageEditorContainerComponent },
@@ -705,11 +919,14 @@ class AXImageEditorContainerComponent extends classes((MXInputBaseValueComponent
705
919
  useExisting: forwardRef(() => AXImageEditorContainerComponent),
706
920
  multi: true,
707
921
  },
708
- ], queries: [{ propertyName: "view", first: true, predicate: AXImageEditorViewComponent, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "imageEditorContainer", first: true, predicate: ["f"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "@if (service.isImageLoad()) {\n <div #f class=\"ax-editor-container ax-default {{ look }}\">\n <ng-content select=\"ax-header\"></ng-content>\n\n <ng-content select=\"ax-image-editor-view\"> </ng-content>\n\n <ng-content select=\"ax-footer\"></ng-content>\n </div>\n <div class=\"ax-error-container\"></div>\n} @else {\n <div class=\"ax-loading-spinner-container\">\n <ax-loading-spinner [size]=\"16\" color=\"primary\" text=\"Loading Image ...\"></ax-loading-spinner>\n </div>\n}\n<ng-content select=\"ax-validation-rule\"> </ng-content>\n\n@if (disabled) {\n <div class=\"ax-disable-overlay\"></div>\n}\n", styles: ["@layer components{ax-image-editor-container{position:relative;display:block;width:fit-content}ax-image-editor-container.ax-state-disabled{opacity:50%}ax-image-editor-container .ax-disable-overlay{position:absolute;top:calc(var(--spacing, .25rem) * 0);left:calc(var(--spacing, .25rem) * 0);height:100%;width:100%;cursor:not-allowed}ax-image-editor-container>.ax-editor-container{display:block;height:auto!important;padding:calc(var(--spacing, .25rem) * 0)}ax-image-editor-container .ax-loading-spinner-container{display:flex;align-items:center;justify-content:center}ax-image-editor-container ax-footer{width:100%}ax-image-editor-container ax-header{width:100%}ax-image-editor-container ax-header ax-toolbar{justify-content:space-between}ax-image-editor-container ax-header .ax-start-toolbar-divider{display:none}}\n/*! tailwindcss v4.1.16 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: AXLoadingSpinnerComponent, selector: "ax-loading-spinner", inputs: ["text", "color", "size", "stroke"] }], encapsulation: i0.ViewEncapsulation.None }); }
922
+ ], queries: [{ propertyName: "view", first: true, predicate: AXImageEditorViewComponent, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "imageEditorContainer", first: true, predicate: ["f"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div #f class=\"ax-editor-container ax-default {{ look }}\" [class.ax-image-editor-pending]=\"!service.isImageLoad()\">\n <ng-content select=\"ax-header\"></ng-content>\n\n <ng-content select=\"ax-image-editor-view\"> </ng-content>\n\n <ng-content select=\"ax-footer\"></ng-content>\n</div>\n\n@if (!service.isImageLoad()) {\n @if (service.loadError()) {\n <div class=\"ax-error-container\">{{ '@acorex:image-editor.load-error' | translate | async }}</div>\n } @else {\n <div class=\"ax-loading-spinner-container\">\n <ax-loading-spinner\n [size]=\"16\"\n color=\"primary\"\n [text]=\"('@acorex:image-editor.loading' | translate | async) || ''\"\n ></ax-loading-spinner>\n </div>\n }\n}\n\n<div class=\"ax-error-container\"></div>\n<ng-content select=\"ax-validation-rule\"> </ng-content>\n\n@if (disabled) {\n <div class=\"ax-disable-overlay\"></div>\n}\n", styles: ["@layer components{ax-image-editor-container{position:relative;display:block;width:fit-content}ax-image-editor-container.ax-state-disabled{opacity:50%}ax-image-editor-container .ax-disable-overlay{position:absolute;top:calc(var(--spacing, .25rem) * 0);left:calc(var(--spacing, .25rem) * 0);height:100%;width:100%;cursor:not-allowed}ax-image-editor-container>.ax-editor-container{display:block;height:auto!important;padding:calc(var(--spacing, .25rem) * 0)}ax-image-editor-container>.ax-editor-container.ax-image-editor-pending{position:absolute;height:calc(var(--spacing, .25rem) * 0);width:calc(var(--spacing, .25rem) * 0);overflow:hidden;opacity:0%}ax-image-editor-container .ax-loading-spinner-container,ax-image-editor-container .ax-error-container{display:flex;min-height:calc(var(--spacing, .25rem) * 40);align-items:center;justify-content:center;padding-inline:calc(var(--spacing, .25rem) * 4);text-align:center}ax-image-editor-container ax-footer{width:100%}ax-image-editor-container ax-header{width:100%}ax-image-editor-container ax-header ax-toolbar{justify-content:space-between}ax-image-editor-container ax-header .ax-start-toolbar-divider{display:none}}\n/*! tailwindcss v4.1.16 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: AXLoadingSpinnerComponent, selector: "ax-loading-spinner", inputs: ["text", "color", "size", "stroke"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
709
923
  }
710
924
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXImageEditorContainerComponent, decorators: [{
711
925
  type: Component,
712
- args: [{ selector: 'ax-image-editor-container', encapsulation: ViewEncapsulation.None, outputs: ['onValueChanged'], inputs: ['look', 'disabled'], providers: [
926
+ args: [{ selector: 'ax-image-editor-container', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, outputs: ['onValueChanged'], inputs: ['look', 'disabled'], host: {
927
+ '[class.ax-state-disabled]': 'disabled',
928
+ '[attr.name]': 'name',
929
+ }, providers: [
713
930
  AXImageEditorService,
714
931
  { provide: AXComponent, useExisting: AXImageEditorContainerComponent },
715
932
  { provide: AXFocusableComponent, useExisting: AXImageEditorContainerComponent },
@@ -719,14 +936,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
719
936
  useExisting: forwardRef(() => AXImageEditorContainerComponent),
720
937
  multi: true,
721
938
  },
722
- ], imports: [AXDecoratorModule, AXLoadingSpinnerComponent], template: "@if (service.isImageLoad()) {\n <div #f class=\"ax-editor-container ax-default {{ look }}\">\n <ng-content select=\"ax-header\"></ng-content>\n\n <ng-content select=\"ax-image-editor-view\"> </ng-content>\n\n <ng-content select=\"ax-footer\"></ng-content>\n </div>\n <div class=\"ax-error-container\"></div>\n} @else {\n <div class=\"ax-loading-spinner-container\">\n <ax-loading-spinner [size]=\"16\" color=\"primary\" text=\"Loading Image ...\"></ax-loading-spinner>\n </div>\n}\n<ng-content select=\"ax-validation-rule\"> </ng-content>\n\n@if (disabled) {\n <div class=\"ax-disable-overlay\"></div>\n}\n", styles: ["@layer components{ax-image-editor-container{position:relative;display:block;width:fit-content}ax-image-editor-container.ax-state-disabled{opacity:50%}ax-image-editor-container .ax-disable-overlay{position:absolute;top:calc(var(--spacing, .25rem) * 0);left:calc(var(--spacing, .25rem) * 0);height:100%;width:100%;cursor:not-allowed}ax-image-editor-container>.ax-editor-container{display:block;height:auto!important;padding:calc(var(--spacing, .25rem) * 0)}ax-image-editor-container .ax-loading-spinner-container{display:flex;align-items:center;justify-content:center}ax-image-editor-container ax-footer{width:100%}ax-image-editor-container ax-header{width:100%}ax-image-editor-container ax-header ax-toolbar{justify-content:space-between}ax-image-editor-container ax-header .ax-start-toolbar-divider{display:none}}\n/*! tailwindcss v4.1.16 | MIT License | https://tailwindcss.com */\n"] }]
723
- }], propDecorators: { imageEditorContainer: [{ type: i0.ViewChild, args: ['f', { isSignal: true }] }], view: [{ type: i0.ContentChild, args: [i0.forwardRef(() => AXImageEditorViewComponent), { isSignal: true }] }], __hostClass: [{
724
- type: HostBinding,
725
- args: ['class']
726
- }], __hostName: [{
727
- type: HostBinding,
728
- args: ['attr.name']
729
- }] } });
939
+ ], imports: [AXDecoratorModule, AXLoadingSpinnerComponent, AsyncPipe, AXTranslatorPipe], template: "<div #f class=\"ax-editor-container ax-default {{ look }}\" [class.ax-image-editor-pending]=\"!service.isImageLoad()\">\n <ng-content select=\"ax-header\"></ng-content>\n\n <ng-content select=\"ax-image-editor-view\"> </ng-content>\n\n <ng-content select=\"ax-footer\"></ng-content>\n</div>\n\n@if (!service.isImageLoad()) {\n @if (service.loadError()) {\n <div class=\"ax-error-container\">{{ '@acorex:image-editor.load-error' | translate | async }}</div>\n } @else {\n <div class=\"ax-loading-spinner-container\">\n <ax-loading-spinner\n [size]=\"16\"\n color=\"primary\"\n [text]=\"('@acorex:image-editor.loading' | translate | async) || ''\"\n ></ax-loading-spinner>\n </div>\n }\n}\n\n<div class=\"ax-error-container\"></div>\n<ng-content select=\"ax-validation-rule\"> </ng-content>\n\n@if (disabled) {\n <div class=\"ax-disable-overlay\"></div>\n}\n", styles: ["@layer components{ax-image-editor-container{position:relative;display:block;width:fit-content}ax-image-editor-container.ax-state-disabled{opacity:50%}ax-image-editor-container .ax-disable-overlay{position:absolute;top:calc(var(--spacing, .25rem) * 0);left:calc(var(--spacing, .25rem) * 0);height:100%;width:100%;cursor:not-allowed}ax-image-editor-container>.ax-editor-container{display:block;height:auto!important;padding:calc(var(--spacing, .25rem) * 0)}ax-image-editor-container>.ax-editor-container.ax-image-editor-pending{position:absolute;height:calc(var(--spacing, .25rem) * 0);width:calc(var(--spacing, .25rem) * 0);overflow:hidden;opacity:0%}ax-image-editor-container .ax-loading-spinner-container,ax-image-editor-container .ax-error-container{display:flex;min-height:calc(var(--spacing, .25rem) * 40);align-items:center;justify-content:center;padding-inline:calc(var(--spacing, .25rem) * 4);text-align:center}ax-image-editor-container ax-footer{width:100%}ax-image-editor-container ax-header{width:100%}ax-image-editor-container ax-header ax-toolbar{justify-content:space-between}ax-image-editor-container ax-header .ax-start-toolbar-divider{display:none}}\n/*! tailwindcss v4.1.16 | MIT License | https://tailwindcss.com */\n"] }]
940
+ }], propDecorators: { imageEditorContainer: [{ type: i0.ViewChild, args: ['f', { isSignal: true }] }], view: [{ type: i0.ContentChild, args: [i0.forwardRef(() => AXImageEditorViewComponent), { isSignal: true }] }] } });
730
941
 
731
942
  const IMAGE_EDITOR_ASPECT_RATIO_ACTIVE_SUFFIX = ':active';
732
943
  function parseAspectRatioItem(item) {
@@ -834,17 +1045,17 @@ class AXImageEditorHistoryComponent {
834
1045
  this.service = inject(AXImageEditorService);
835
1046
  }
836
1047
  UndoHandler() {
837
- this.service.undoPressed.set(true);
1048
+ this.service.undo();
838
1049
  }
839
1050
  RedoHandler() {
840
- this.service.redoPressed.set(true);
1051
+ this.service.redo();
841
1052
  }
842
1053
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXImageEditorHistoryComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
843
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.8", type: AXImageEditorHistoryComponent, isStandalone: true, selector: "ax-image-editor-history", providers: [{ provide: AXComponent, useExisting: AXImageEditorHistoryComponent }], ngImport: i0, template: "<div class=\"flex\">\n <ax-button\n class=\"ax-sm\"\n [axTooltip]=\"'@acorex:image-editor.undo' | translate | async\"\n axTooltipPlacement=\"top\"\n (onClick)=\"UndoHandler()\"\n look=\"blank\"\n >\n <ax-icon class=\"ax-icon ax-icon-undo\"></ax-icon>\n </ax-button>\n\n <ax-button\n class=\"ax-sm\"\n [axTooltip]=\"'@acorex:image-editor.redo' | translate | async\"\n axTooltipPlacement=\"top\"\n (onClick)=\"RedoHandler()\"\n look=\"blank\"\n >\n <ax-icon class=\"ax-icon ax-icon-redo\"></ax-icon>\n </ax-button>\n</div>\n", dependencies: [{ kind: "component", type: AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "directive", type: AXTooltipDirective, selector: "[axTooltip]", inputs: ["axTooltipDisabled", "axTooltip", "axTooltipContext", "axTooltipPlacement", "axTooltipOffsetX", "axTooltipOffsetY", "axTooltipOpenAfter", "axTooltipCloseAfter"] }, { kind: "ngmodule", type: AXTooltipModule }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: AXTranslatorPipe, name: "translate" }], encapsulation: i0.ViewEncapsulation.None }); }
1054
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.8", type: AXImageEditorHistoryComponent, isStandalone: true, selector: "ax-image-editor-history", providers: [{ provide: AXComponent, useExisting: AXImageEditorHistoryComponent }], ngImport: i0, template: "<div class=\"flex\">\n <ax-button\n class=\"ax-sm\"\n [axTooltip]=\"'@acorex:image-editor.undo' | translate | async\"\n axTooltipPlacement=\"top\"\n (onClick)=\"UndoHandler()\"\n look=\"blank\"\n [disabled]=\"!service.canUndo()\"\n >\n <ax-icon class=\"ax-icon ax-icon-undo\"></ax-icon>\n </ax-button>\n\n <ax-button\n class=\"ax-sm\"\n [axTooltip]=\"'@acorex:image-editor.redo' | translate | async\"\n axTooltipPlacement=\"top\"\n (onClick)=\"RedoHandler()\"\n look=\"blank\"\n [disabled]=\"!service.canRedo()\"\n >\n <ax-icon class=\"ax-icon ax-icon-redo\"></ax-icon>\n </ax-button>\n</div>\n", dependencies: [{ kind: "component", type: AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i1.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "directive", type: AXTooltipDirective, selector: "[axTooltip]", inputs: ["axTooltipDisabled", "axTooltip", "axTooltipContext", "axTooltipPlacement", "axTooltipOffsetX", "axTooltipOffsetY", "axTooltipOpenAfter", "axTooltipCloseAfter"] }, { kind: "ngmodule", type: AXTooltipModule }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
844
1055
  }
845
1056
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXImageEditorHistoryComponent, decorators: [{
846
1057
  type: Component,
847
- args: [{ selector: 'ax-image-editor-history', encapsulation: ViewEncapsulation.None, imports: [AXButtonComponent, AXDecoratorModule, AXTooltipDirective, AsyncPipe, AXTranslatorPipe, AXTooltipModule], providers: [{ provide: AXComponent, useExisting: AXImageEditorHistoryComponent }], template: "<div class=\"flex\">\n <ax-button\n class=\"ax-sm\"\n [axTooltip]=\"'@acorex:image-editor.undo' | translate | async\"\n axTooltipPlacement=\"top\"\n (onClick)=\"UndoHandler()\"\n look=\"blank\"\n >\n <ax-icon class=\"ax-icon ax-icon-undo\"></ax-icon>\n </ax-button>\n\n <ax-button\n class=\"ax-sm\"\n [axTooltip]=\"'@acorex:image-editor.redo' | translate | async\"\n axTooltipPlacement=\"top\"\n (onClick)=\"RedoHandler()\"\n look=\"blank\"\n >\n <ax-icon class=\"ax-icon ax-icon-redo\"></ax-icon>\n </ax-button>\n</div>\n" }]
1058
+ args: [{ selector: 'ax-image-editor-history', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [AXButtonComponent, AXDecoratorModule, AXTooltipDirective, AsyncPipe, AXTranslatorPipe, AXTooltipModule], providers: [{ provide: AXComponent, useExisting: AXImageEditorHistoryComponent }], template: "<div class=\"flex\">\n <ax-button\n class=\"ax-sm\"\n [axTooltip]=\"'@acorex:image-editor.undo' | translate | async\"\n axTooltipPlacement=\"top\"\n (onClick)=\"UndoHandler()\"\n look=\"blank\"\n [disabled]=\"!service.canUndo()\"\n >\n <ax-icon class=\"ax-icon ax-icon-undo\"></ax-icon>\n </ax-button>\n\n <ax-button\n class=\"ax-sm\"\n [axTooltip]=\"'@acorex:image-editor.redo' | translate | async\"\n axTooltipPlacement=\"top\"\n (onClick)=\"RedoHandler()\"\n look=\"blank\"\n [disabled]=\"!service.canRedo()\"\n >\n <ax-icon class=\"ax-icon ax-icon-redo\"></ax-icon>\n </ax-button>\n</div>\n" }]
848
1059
  }] });
849
1060
 
850
1061
  /**
@@ -1061,7 +1272,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
1061
1272
  ], providers: [{ provide: AXComponent, useExisting: AXImageEditorToolSelectorComponent }], template: "<ax-image-editor-tools-bar>\n <ax-image-editor-pen></ax-image-editor-pen>\n <ax-image-editor-highlight></ax-image-editor-highlight>\n <ax-image-editor-crop [aspectRatio]=\"aspectRatio()\"></ax-image-editor-crop>\n <ax-image-editor-rotate></ax-image-editor-rotate>\n</ax-image-editor-tools-bar>\n" }]
1062
1273
  }], propDecorators: { aspectRatio: [{ type: i0.Input, args: [{ isSignal: true, alias: "aspectRatio", required: false }] }] } });
1063
1274
 
1064
- const COMPONENT = [
1275
+ const PUBLIC_COMPONENTS = [
1065
1276
  AXImageEditorViewComponent,
1066
1277
  AXImageEditorContainerComponent,
1067
1278
  AXImageEditorCropComponent,
@@ -1073,28 +1284,9 @@ const COMPONENT = [
1073
1284
  AXImageEditorRotateComponent,
1074
1285
  AXImageEditorCropperWindowComponent,
1075
1286
  ];
1076
- const MODULES = [
1077
- AXButtonModule,
1078
- FormsModule,
1079
- AXRangeSliderModule,
1080
- AXSelectBoxModule,
1081
- AXColorBoxModule,
1082
- AXPopoverModule,
1083
- AXDecoratorModule,
1084
- AXLoadingModule,
1085
- AXDialogModule,
1086
- ];
1087
1287
  class AXImageEditorModule {
1088
1288
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXImageEditorModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
1089
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.8", ngImport: i0, type: AXImageEditorModule, imports: [AXButtonModule,
1090
- FormsModule,
1091
- AXRangeSliderModule,
1092
- AXSelectBoxModule,
1093
- AXColorBoxModule,
1094
- AXPopoverModule,
1095
- AXDecoratorModule,
1096
- AXLoadingModule,
1097
- AXDialogModule, AXImageEditorViewComponent,
1289
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.8", ngImport: i0, type: AXImageEditorModule, imports: [AXImageEditorViewComponent,
1098
1290
  AXImageEditorContainerComponent,
1099
1291
  AXImageEditorCropComponent,
1100
1292
  AXImageEditorHistoryComponent,
@@ -1113,7 +1305,7 @@ class AXImageEditorModule {
1113
1305
  AXImageEditorHighlightComponent,
1114
1306
  AXImageEditorRotateComponent,
1115
1307
  AXImageEditorCropperWindowComponent] }); }
1116
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXImageEditorModule, imports: [MODULES, AXImageEditorContainerComponent,
1308
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXImageEditorModule, imports: [AXImageEditorContainerComponent,
1117
1309
  AXImageEditorCropComponent,
1118
1310
  AXImageEditorHistoryComponent,
1119
1311
  AXImageEditorToolSelectorComponent,
@@ -1125,9 +1317,8 @@ class AXImageEditorModule {
1125
1317
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXImageEditorModule, decorators: [{
1126
1318
  type: NgModule,
1127
1319
  args: [{
1128
- imports: [...MODULES, ...COMPONENT],
1129
- exports: [...COMPONENT],
1130
- providers: [],
1320
+ imports: [...PUBLIC_COMPONENTS],
1321
+ exports: [...PUBLIC_COMPONENTS],
1131
1322
  }]
1132
1323
  }] });
1133
1324