canvico-editor 1.0.2 → 2.0.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 (45) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +31 -8
  3. package/dist/CanvicoEditor.d.ts +45 -24
  4. package/dist/CanvicoEditor.d.ts.map +1 -1
  5. package/dist/CanvicoEditor.js +262 -87
  6. package/dist/CanvicoEditor.js.map +1 -1
  7. package/dist/modules/BaseModule.d.ts +5 -4
  8. package/dist/modules/BaseModule.d.ts.map +1 -1
  9. package/dist/modules/BaseModule.js +7 -6
  10. package/dist/modules/BaseModule.js.map +1 -1
  11. package/dist/modules/CropModule.d.ts +32 -31
  12. package/dist/modules/CropModule.d.ts.map +1 -1
  13. package/dist/modules/CropModule.js +147 -106
  14. package/dist/modules/CropModule.js.map +1 -1
  15. package/dist/modules/ResizeModule.d.ts +12 -26
  16. package/dist/modules/ResizeModule.d.ts.map +1 -1
  17. package/dist/modules/ResizeModule.js +27 -35
  18. package/dist/modules/ResizeModule.js.map +1 -1
  19. package/dist/modules/TransformModule.d.ts +38 -0
  20. package/dist/modules/TransformModule.d.ts.map +1 -0
  21. package/dist/modules/TransformModule.js +55 -0
  22. package/dist/modules/TransformModule.js.map +1 -0
  23. package/dist/state/CanvasState.d.ts +34 -0
  24. package/dist/state/CanvasState.d.ts.map +1 -0
  25. package/dist/state/CanvasState.js +67 -0
  26. package/dist/state/CanvasState.js.map +1 -0
  27. package/dist/state/EditorReducer.d.ts +60 -0
  28. package/dist/state/EditorReducer.d.ts.map +1 -0
  29. package/dist/state/EditorReducer.js +127 -0
  30. package/dist/state/EditorReducer.js.map +1 -0
  31. package/dist/types.d.ts +33 -0
  32. package/dist/types.d.ts.map +1 -1
  33. package/dist/utils/dom-manager.d.ts +12 -1
  34. package/dist/utils/dom-manager.d.ts.map +1 -1
  35. package/dist/utils/dom-manager.js +22 -14
  36. package/dist/utils/dom-manager.js.map +1 -1
  37. package/dist/utils/error-handler.d.ts +18 -7
  38. package/dist/utils/error-handler.d.ts.map +1 -1
  39. package/dist/utils/error-handler.js +34 -7
  40. package/dist/utils/error-handler.js.map +1 -1
  41. package/dist/utils/validation.d.ts +0 -7
  42. package/dist/utils/validation.d.ts.map +1 -1
  43. package/dist/utils/validation.js +33 -29
  44. package/dist/utils/validation.js.map +1 -1
  45. package/package.json +6 -4
@@ -1,44 +1,41 @@
1
1
  import { BaseModule as g } from "./BaseModule.js";
2
- var p = /* @__PURE__ */ ((h) => (h.NW = "nw", h.NE = "ne", h.SW = "sw", h.SE = "se", h))(p || {});
3
- class w extends g {
2
+ var l = /* @__PURE__ */ ((n) => (n.NW = "nw", n.NE = "ne", n.SW = "sw", n.SE = "se", n))(l || {});
3
+ class p extends g {
4
4
  // --- PROPERTIES ---
5
5
  /** Core Dependencies */
6
6
  canvas;
7
7
  ctx;
8
8
  applyButton;
9
- getCurrentImage;
10
- requestRedraw;
9
+ state;
11
10
  onCropAppliedCallback;
12
11
  // Configuration
13
12
  frameColor;
14
13
  outsideOverlayColor;
15
14
  HANDLE_SIZE = 8;
16
15
  MIN_CROP_SIZE = this.HANDLE_SIZE * 2;
17
- // State
18
- cropMode = !1;
19
- cropRect = { x: 0, y: 0, w: 0, h: 0 };
16
+ isLocalActive = !1;
20
17
  dragging = !1;
21
18
  dragOffsetX = 0;
22
19
  dragOffsetY = 0;
23
- /** The handle currently being dragged, or "rect" for the whole rectangle. */
24
20
  activeHandle = null;
25
21
  shiftPressed = !1;
26
22
  lastMouseX = 0;
27
23
  lastMouseY = 0;
24
+ previousTouchAction = "";
28
25
  // --- CONSTRUCTOR & LIFECYCLE METHODS ---
29
26
  /**
30
27
  * Creates an instance of the CropModule.
31
28
  * @param elements - The DOM elements used by the module.
32
29
  * @param options - The options for configuring the crop module.
33
30
  */
34
- constructor(t, e) {
35
- super("crop"), this.canvas = e.canvas, this.ctx = e.ctx, this.getCurrentImage = e.getCurrentImage, this.requestRedraw = e.requestRedraw, this.onCropAppliedCallback = e.onCropApplied, this.applyButton = t.applyButton, this.frameColor = e.frameColor || "red", this.outsideOverlayColor = e.outsideOverlayColor || "rgba(0, 0, 0, 0.2)";
31
+ constructor(t, s) {
32
+ super("crop"), this.canvas = s.canvas, this.ctx = s.ctx, this.state = s.state, this.onCropAppliedCallback = s.onCropApplied, this.applyButton = t.applyButton, this.frameColor = s.frameColor || "red", this.outsideOverlayColor = s.outsideOverlayColor || "rgba(0, 0, 0, 0.2)";
36
33
  }
37
34
  /**
38
35
  * Initializes the module by attaching event listeners.
39
36
  */
40
37
  init() {
41
- this.addEventListener(this.applyButton, "click", this._applyCrop);
38
+ this.addEventListener(this.applyButton, "click", this._applyCrop), this.addEventListener(this.canvas, "mousedown", this._onMouseDown), this.addEventListener(this.canvas, "mousemove", this._onMouseMove), this.addEventListener(globalThis, "mouseup", this._onMouseUp), this.addEventListener(globalThis, "keydown", this._onKeyDown), this.addEventListener(globalThis, "keyup", this._onKeyUp), this.addEventListener(this.canvas, "touchstart", this._onTouchStart, { passive: !1 }), this.addEventListener(this.canvas, "touchmove", this._onTouchMove, { passive: !1 }), this.addEventListener(globalThis, "touchend", this._onTouchEnd, { passive: !1 }), this.addEventListener(globalThis, "touchcancel", this._onTouchCancel, { passive: !1 });
42
39
  }
43
40
  /**
44
41
  * Cleans up resources and event listeners.
@@ -51,84 +48,109 @@ class w extends g {
51
48
  * Activates the crop module, making it ready for user interaction.
52
49
  */
53
50
  activate() {
54
- this.enableCropMode();
51
+ this._enableCropMode();
55
52
  }
56
53
  /**
57
54
  * Deactivates the crop module.
58
55
  */
59
56
  deactivate() {
60
- this.disableCropMode();
61
- }
62
- /**
63
- * Checks if the crop mode is currently active.
64
- * @returns True if crop mode is active, false otherwise.
65
- */
66
- isCropModeActive() {
67
- return this.cropMode;
57
+ this._disableCropMode();
68
58
  }
69
59
  /**
70
60
  * Enables crop mode.
71
- * This initializes the crop rectangle to a default size and adds all necessary event listeners for interaction.
72
- * Enables crop mode, initializing the crop rectangle and adding event listeners.
61
+ * This initializes the crop rectangle to a default size.
73
62
  */
74
- enableCropMode() {
75
- if (!this.getCurrentImage() || this.cropMode)
63
+ _enableCropMode() {
64
+ if (this.isLocalActive || !this.state.getCurrent())
76
65
  return;
77
- this.cropMode = !0;
78
- const t = this.canvas.width, e = this.canvas.height;
79
- this.cropRect = { x: t / 4, y: e / 4, w: t / 2, h: e / 2 }, this.shiftPressed = !1, this.canvas.addEventListener("mousedown", this._onMouseDown), this.canvas.addEventListener("mousemove", this._onMouseMove), window.addEventListener("mouseup", this._onMouseUp), window.addEventListener("keydown", this.onKeyDown), window.addEventListener("keyup", this._onKeyUp), this.requestRedraw?.();
66
+ this.isLocalActive = !0, this.dragging = !1, this.dragOffsetX = 0, this.dragOffsetY = 0, this.activeHandle = null, this.shiftPressed = !1, this.lastMouseX = 0, this.lastMouseY = 0, this.previousTouchAction = this.canvas.style.touchAction, this.canvas.style.touchAction = "none";
67
+ const t = this.canvas.width, s = this.canvas.height, e = Math.min(t, s) * 0.6;
68
+ this.state.setCropState({
69
+ active: !0,
70
+ rect: { x: (t - e) / 2, y: (s - e) / 2, w: e, h: e }
71
+ });
80
72
  }
81
73
  /**
82
74
  * Disables crop mode.
83
- * This resets the module's state and removes all event listeners related to cropping.
84
- * Disables crop mode, resetting state and removing event listeners.
75
+ * This resets the module's state.
85
76
  */
86
- disableCropMode() {
87
- this.cropMode && (this.cropMode = !1, this.dragging = !1, this.activeHandle = null, this.canvas.removeEventListener("mousedown", this._onMouseDown), this.canvas.removeEventListener("mousemove", this._onMouseMove), window.removeEventListener("mouseup", this._onMouseUp), window.removeEventListener("keydown", this.onKeyDown), window.removeEventListener("keyup", this._onKeyUp), this.canvas.style.cursor = "default", this.requestRedraw?.());
77
+ _disableCropMode() {
78
+ if (!this.isLocalActive && !this.state.getCropState().active) return;
79
+ this.isLocalActive = !1, this.dragging = !1, this.dragOffsetX = 0, this.dragOffsetY = 0, this.activeHandle = null, this.shiftPressed = !1, this.lastMouseX = 0, this.lastMouseY = 0, this.canvas.style.touchAction = this.previousTouchAction;
80
+ const t = this._getCropRect();
81
+ this.state.setCropState({ active: !1, rect: { ...t } }), this.canvas.style.cursor = "default";
88
82
  }
89
83
  // --- EVENT HANDLERS ---
90
84
  /**
91
85
  * Handles the keydown event, specifically for the Shift key to toggle aspect ratio lock.
92
86
  * @internal
93
87
  */
94
- onKeyDown = (t) => {
95
- t.key === "Shift" && (this.shiftPressed = !0, this.dragging && this._handleDrag(this.lastMouseX, this.lastMouseY));
88
+ _onKeyDown = (t) => {
89
+ this.state.getCropState().active && t.key === "Shift" && (this.shiftPressed = !0, this.dragging && this._handleDrag(this.lastMouseX, this.lastMouseY));
96
90
  };
97
91
  /**
98
92
  * Handles the keyup event, specifically for the Shift key to toggle aspect ratio lock.
99
93
  * @param e - The keyboard event.
100
94
  */
101
95
  _onKeyUp = (t) => {
102
- t.key === "Shift" && this.cropMode && (this.shiftPressed = !1, this.dragging && this._handleDrag(this.lastMouseX, this.lastMouseY));
96
+ t.key === "Shift" && this.state.getCropState().active && (this.shiftPressed = !1, this.dragging && this._handleDrag(this.lastMouseX, this.lastMouseY));
103
97
  };
104
98
  /**
105
99
  * Handles the mousedown event to initiate dragging or resizing of the crop rectangle.
106
100
  * @param event - The mouse event.
107
101
  */
108
102
  _onMouseDown = (t) => {
109
- if (!this.cropMode) return;
110
- const e = t.offsetX, s = t.offsetY;
111
- this.activeHandle = this._detectHandle(e, s), this.activeHandle && (this.dragging = !0, this.activeHandle === "rect" && (this.dragOffsetX = e - this.cropRect.x, this.dragOffsetY = s - this.cropRect.y));
103
+ if (!this.state.getCropState().active) return;
104
+ const e = t.offsetX, i = t.offsetY, a = this._detectHandle(e, i);
105
+ if (a) {
106
+ const h = this._getCropRect();
107
+ this.dragging = !0, this.activeHandle = a, this.dragOffsetX = a === "rect" ? e - h.x : 0, this.dragOffsetY = a === "rect" ? i - h.y : 0;
108
+ }
112
109
  };
113
110
  /**
114
111
  * Handles the mousemove event to update the crop rectangle during drag/resize and to update the cursor style.
115
112
  * @param event - The mouse event.
116
113
  */
117
114
  _onMouseMove = (t) => {
118
- if (this.lastMouseX = t.offsetX, this.lastMouseY = t.offsetY, !this.cropMode || !this.dragging || !this.activeHandle) {
119
- if (this.cropMode) {
115
+ if (this.state.getCropState().active) {
116
+ if (this.lastMouseX = t.offsetX, this.lastMouseY = t.offsetY, !this.dragging || !this.activeHandle) {
120
117
  const e = this._detectHandle(t.offsetX, t.offsetY);
121
118
  this._updateCursor(e);
119
+ return;
122
120
  }
123
- return;
121
+ this._handleDrag(this.lastMouseX, this.lastMouseY);
124
122
  }
125
- this._handleDrag(this.lastMouseX, this.lastMouseY);
126
123
  };
127
124
  /**
128
125
  * Handles the mouseup event to finalize the drag/resize operation and normalize the crop rectangle.
129
126
  */
130
127
  _onMouseUp = () => {
131
- !this.cropMode || !this.dragging || (this.dragging = !1, this.activeHandle = null, this._normalizeCropRect(), this.requestRedraw?.());
128
+ !this.state.getCropState().active || !this.dragging || (this.dragging = !1, this.activeHandle = null, this._normalizeCropRect());
129
+ };
130
+ _onTouchStart = (t) => {
131
+ if (!this.state.getCropState().active)
132
+ return;
133
+ const e = this._getTouchPoint(t);
134
+ if (!e)
135
+ return;
136
+ t.preventDefault();
137
+ const i = this._detectHandle(e.x, e.y);
138
+ if (i) {
139
+ const a = this._getCropRect();
140
+ this.dragging = !0, this.activeHandle = i, this.dragOffsetX = i === "rect" ? e.x - a.x : 0, this.dragOffsetY = i === "rect" ? e.y - a.y : 0, this.lastMouseX = e.x, this.lastMouseY = e.y;
141
+ }
142
+ };
143
+ _onTouchMove = (t) => {
144
+ if (!this.state.getCropState().active)
145
+ return;
146
+ const e = this._getTouchPoint(t);
147
+ e && (t.preventDefault(), this.lastMouseX = e.x, this.lastMouseY = e.y, !(!this.dragging || !this.activeHandle) && this._handleDrag(this.lastMouseX, this.lastMouseY));
148
+ };
149
+ _onTouchEnd = (t) => {
150
+ !this.state.getCropState().active || !this.dragging || (t.preventDefault(), this.dragging = !1, this.activeHandle = null, this._normalizeCropRect());
151
+ };
152
+ _onTouchCancel = (t) => {
153
+ !this.state.getCropState().active || !this.dragging || (t.preventDefault(), this.dragging = !1, this.activeHandle = null, this._normalizeCropRect());
132
154
  };
133
155
  // --- PRIVATE LOGIC ---
134
156
  /**
@@ -136,74 +158,45 @@ class w extends g {
136
158
  * @param mx - The current mouse X position.
137
159
  * @param my - The current mouse Y position.
138
160
  */
139
- _handleDrag(t, e) {
140
- let { x: s, y: i, w: o, h: r } = this.cropRect;
141
- if (this.activeHandle === "rect")
142
- s = t - this.dragOffsetX, i = e - this.dragOffsetY;
143
- else {
144
- switch (this.activeHandle) {
145
- case "nw":
146
- o += s - t, r += i - e, s = t, i = e;
147
- break;
148
- case "ne":
149
- o = t - s, r += i - e, i = e;
150
- break;
151
- case "sw":
152
- o += s - t, r = e - i, s = t;
153
- break;
154
- case "se":
155
- o = t - s, r = e - i;
156
- break;
157
- }
158
- if (this.shiftPressed) {
159
- const c = Math.abs(o), n = Math.abs(r), d = o < 0 ? -1 : 1, l = r < 0 ? -1 : 1;
160
- c > n ? r = l * c : o = d * n;
161
- }
162
- }
163
- this.cropRect = { x: s, y: i, w: o, h: r }, this.requestRedraw?.();
161
+ _handleDrag(t, s) {
162
+ if (!this.activeHandle)
163
+ return;
164
+ const e = this.activeHandle === "rect" ? this._moveRect(this._getCropRect(), t, s, this.dragOffsetX, this.dragOffsetY) : this._resizeRect(this._getCropRect(), t, s, this.activeHandle, this.shiftPressed);
165
+ this._setCropRect(e);
164
166
  }
165
167
  /**
166
168
  * Normalizes the crop rectangle after a drag/resize operation.
167
169
  * Ensures width and height are positive and clamps the rectangle to canvas boundaries.
168
170
  */
169
171
  _normalizeCropRect() {
170
- this.cropRect.w < 0 && (this.cropRect.x += this.cropRect.w, this.cropRect.w = Math.abs(this.cropRect.w)), this.cropRect.h < 0 && (this.cropRect.y += this.cropRect.h, this.cropRect.h = Math.abs(this.cropRect.h)), this.cropRect.w < this.MIN_CROP_SIZE && (this.cropRect.w = this.MIN_CROP_SIZE), this.cropRect.h < this.MIN_CROP_SIZE && (this.cropRect.h = this.MIN_CROP_SIZE), this.cropRect.x = Math.max(0, this.cropRect.x), this.cropRect.y = Math.max(0, this.cropRect.y), this.cropRect.x + this.cropRect.w > this.canvas.width && (this.cropRect.w = this.canvas.width - this.cropRect.x), this.cropRect.y + this.cropRect.h > this.canvas.height && (this.cropRect.h = this.canvas.height - this.cropRect.y);
172
+ const t = { ...this._getCropRect() }, s = Math.max(1, this.canvas.width), e = Math.max(1, this.canvas.height), i = Math.min(this.MIN_CROP_SIZE, s), a = Math.min(this.MIN_CROP_SIZE, e), h = (c, u, d) => Math.min(Math.max(c, u), d);
173
+ t.w < 0 && (t.x += t.w, t.w = Math.abs(t.w)), t.h < 0 && (t.y += t.h, t.h = Math.abs(t.h)), t.x = h(t.x, 0, s - i), t.y = h(t.y, 0, e - a);
174
+ const o = s - t.x, r = e - t.y;
175
+ t.w = h(t.w, i, o), t.h = h(t.h, a, r), this._setCropRect(t);
171
176
  }
172
177
  /**
173
178
  * Applies the crop and provides the new image data URL to the callback.
174
179
  */
175
180
  _applyCrop = () => {
176
- const t = this.getCurrentImage();
177
- if (!t || !this.cropMode || this.cropRect.w === 0 || this.cropRect.h === 0) {
181
+ const t = this._getCropRect();
182
+ if (!this.state.getCropState().active || t.w === 0 || t.h === 0) {
178
183
  this.deactivate();
179
184
  return;
180
185
  }
181
- const { x: e, y: s, w: i, h: o } = this._getVisualRect(), r = t.naturalWidth / this.canvas.width, a = t.naturalHeight / this.canvas.height, c = e * r, n = s * a, d = i * r, l = o * a;
182
- if (d <= 0 || l <= 0) {
183
- console.error("Crop dimensions are invalid for applying crop."), this.deactivate();
184
- return;
185
- }
186
- const u = document.createElement("canvas");
187
- u.width = d, u.height = l;
188
- const f = u.getContext("2d");
189
- if (!f) {
190
- console.error("Failed to get 2D context for offscreen canvas");
191
- return;
192
- }
193
- f.drawImage(t, c, n, d, l, 0, 0, d, l), this.onCropAppliedCallback(u.toDataURL());
186
+ this.onCropAppliedCallback();
194
187
  };
195
188
  // --- DRAWING & HELPER METHODS ---
196
189
  /**
197
190
  * Draws the crop overlay, including the semi-transparent mask, border, and handles.
198
191
  */
199
192
  drawOverlay() {
200
- if (!this.cropMode)
193
+ if (!this.state.getCropState().active)
201
194
  return;
202
- const { x: t, y: e, w: s, h: i } = this._getVisualRect();
203
- this.ctx.save(), this.ctx.fillStyle = this.outsideOverlayColor, this.ctx.beginPath(), this.ctx.rect(0, 0, this.canvas.width, this.canvas.height), this.ctx.rect(t, e, s, i), this.ctx.fill("evenodd"), this.ctx.strokeStyle = this.frameColor, this.ctx.lineWidth = 2, this.ctx.strokeRect(t, e, s, i), this.ctx.fillStyle = this.frameColor;
204
- for (const o of Object.values(p)) {
205
- const { x: r, y: a } = this._getVisualHandleCoord(o);
206
- this.ctx.fillRect(r - this.HANDLE_SIZE / 2, a - this.HANDLE_SIZE / 2, this.HANDLE_SIZE, this.HANDLE_SIZE);
195
+ const { x: t, y: s, w: e, h: i } = this._getVisualRect();
196
+ this.ctx.save(), this.ctx.fillStyle = this.outsideOverlayColor, this.ctx.beginPath(), this.ctx.rect(0, 0, this.canvas.width, this.canvas.height), this.ctx.rect(t, s, e, i), this.ctx.fill("evenodd"), this.ctx.strokeStyle = this.frameColor, this.ctx.lineWidth = 2, this.ctx.strokeRect(t, s, e, i), this.ctx.fillStyle = this.frameColor;
197
+ for (const a of Object.values(l)) {
198
+ const { x: h, y: o } = this._getVisualHandleCoord(a);
199
+ this.ctx.fillRect(h - this.HANDLE_SIZE / 2, o - this.HANDLE_SIZE / 2, this.HANDLE_SIZE, this.HANDLE_SIZE);
207
200
  }
208
201
  this.ctx.restore();
209
202
  }
@@ -213,25 +206,25 @@ class w extends g {
213
206
  * @param my - The mouse Y position.
214
207
  * @returns The active handle, "rect" if inside the rectangle, otherwise null.
215
208
  */
216
- _detectHandle(t, e) {
217
- for (const a of Object.values(p)) {
218
- const { x: c, y: n } = this._getVisualHandleCoord(a);
219
- if (Math.abs(t - c) <= this.HANDLE_SIZE && Math.abs(e - n) <= this.HANDLE_SIZE)
220
- return a;
209
+ _detectHandle(t, s) {
210
+ for (const o of Object.values(l)) {
211
+ const { x: r, y: c } = this._getVisualHandleCoord(o);
212
+ if (Math.abs(t - r) <= this.HANDLE_SIZE && Math.abs(s - c) <= this.HANDLE_SIZE)
213
+ return o;
221
214
  }
222
- const { x: s, y: i, w: o, h: r } = this._getVisualRect();
223
- return t >= s && t <= s + o && e >= i && e <= i + r ? "rect" : null;
215
+ const { x: e, y: i, w: a, h } = this._getVisualRect();
216
+ return t >= e && t <= e + a && s >= i && s <= i + h ? "rect" : null;
224
217
  }
225
218
  /**
226
219
  * Gets the visual representation of the crop rectangle, ensuring positive width and height.
227
220
  * @returns A rectangle with positive dimensions for drawing and hit detection.
228
221
  */
229
222
  _getVisualRect() {
230
- const { x: t, y: e, w: s, h: i } = this.cropRect;
223
+ const { x: t, y: s, w: e, h: i } = this._getCropRect();
231
224
  return {
232
- x: s > 0 ? t : t + s,
233
- y: i > 0 ? e : e + i,
234
- w: Math.abs(s),
225
+ x: e > 0 ? t : t + e,
226
+ y: i > 0 ? s : s + i,
227
+ w: Math.abs(e),
235
228
  h: Math.abs(i)
236
229
  };
237
230
  }
@@ -241,16 +234,16 @@ class w extends g {
241
234
  * @returns The visual coordinates of the handle.
242
235
  */
243
236
  _getVisualHandleCoord(t) {
244
- const { x: e, y: s, w: i, h: o } = this._getVisualRect();
237
+ const { x: s, y: e, w: i, h: a } = this._getVisualRect();
245
238
  switch (t) {
246
239
  case "nw":
247
- return { x: e, y: s };
240
+ return { x: s, y: e };
248
241
  case "ne":
249
- return { x: e + i, y: s };
242
+ return { x: s + i, y: e };
250
243
  case "sw":
251
- return { x: e, y: s + o };
244
+ return { x: s, y: e + a };
252
245
  case "se":
253
- return { x: e + i, y: s + o };
246
+ return { x: s + i, y: e + a };
254
247
  }
255
248
  }
256
249
  /**
@@ -260,8 +253,56 @@ class w extends g {
260
253
  _updateCursor(t) {
261
254
  t === "rect" ? this.canvas.style.cursor = "move" : t ? this.canvas.style.cursor = "crosshair" : this.canvas.style.cursor = "default";
262
255
  }
256
+ _getCropRect() {
257
+ return this.state.getCropState().rect;
258
+ }
259
+ _setCropRect(t) {
260
+ this.state.setCropState({ rect: t });
261
+ }
262
+ _getTouchPoint(t) {
263
+ const s = t.touches[0] || t.changedTouches[0];
264
+ if (!s)
265
+ return null;
266
+ const e = this.canvas.getBoundingClientRect();
267
+ if (e.width <= 0 || e.height <= 0)
268
+ return null;
269
+ const i = this.canvas.width / e.width, a = this.canvas.height / e.height;
270
+ return {
271
+ x: (s.clientX - e.left) * i,
272
+ y: (s.clientY - e.top) * a
273
+ };
274
+ }
275
+ _moveRect(t, s, e, i, a) {
276
+ return {
277
+ ...t,
278
+ x: s - i,
279
+ y: e - a
280
+ };
281
+ }
282
+ _resizeRect(t, s, e, i, a) {
283
+ let { x: h, y: o, w: r, h: c } = t;
284
+ switch (i) {
285
+ case "nw":
286
+ r += h - s, c += o - e, h = s, o = e;
287
+ break;
288
+ case "ne":
289
+ r = s - h, c += o - e, o = e;
290
+ break;
291
+ case "sw":
292
+ r += h - s, c = e - o, h = s;
293
+ break;
294
+ case "se":
295
+ r = s - h, c = e - o;
296
+ break;
297
+ }
298
+ return a ? this._asSquareRect(h, o, r, c) : { x: h, y: o, w: r, h: c };
299
+ }
300
+ _asSquareRect(t, s, e, i) {
301
+ const a = Math.abs(e), h = Math.abs(i), o = e < 0 ? -1 : 1, r = i < 0 ? -1 : 1;
302
+ return a > h ? i = r * a : e = o * h, { x: t, y: s, w: e, h: i };
303
+ }
263
304
  }
264
305
  export {
265
- w as CropModule
306
+ p as CropModule
266
307
  };
267
308
  //# sourceMappingURL=CropModule.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CropModule.js","sources":["../../src/modules/CropModule.ts"],"sourcesContent":["import type { CropDOMElements } from \"../utils/dom-manager.js\";\r\nimport { BaseModule } from \"./BaseModule.js\";\r\n\r\n// --- TYPE DEFINITIONS ---\r\n\r\n/**\r\n * Configuration options for the CropModule.\r\n */\r\nexport interface CropModuleOptions {\r\n canvas: HTMLCanvasElement;\r\n ctx: CanvasRenderingContext2D;\r\n getCurrentImage: () => HTMLImageElement | undefined;\r\n requestRedraw?: () => void;\r\n onCropApplied: (newImageDataUrl: string) => void;\r\n frameColor?: string;\r\n outsideOverlayColor?: string;\r\n}\r\n\r\n/**\r\n * Enum representing the four resize handles of the crop rectangle.\r\n */\r\n\r\nenum Handle {\r\n NW = \"nw\",\r\n NE = \"ne\",\r\n SW = \"sw\",\r\n SE = \"se\",\r\n}\r\n\r\n/**\r\n * Represents a rectangle with position (x, y) and dimensions (w, h).\r\n */\r\n\r\ninterface Rect {\r\n x: number;\r\n y: number;\r\n w: number;\r\n h: number;\r\n}\r\n\r\n/**\r\n * A module for cropping images on the canvas. It provides a draggable and resizable\r\n * rectangle to define the crop area.\r\n */\r\n\r\nexport class CropModule extends BaseModule {\r\n // --- PROPERTIES ---\r\n\r\n /** Core Dependencies */\r\n private canvas: HTMLCanvasElement;\r\n\r\n private ctx: CanvasRenderingContext2D;\r\n\r\n private applyButton: HTMLElement;\r\n\r\n private getCurrentImage: () => HTMLImageElement | undefined;\r\n\r\n private requestRedraw?: () => void;\r\n\r\n private onCropAppliedCallback: (newImageDataUrl: string) => void;\r\n\r\n // Configuration\r\n private frameColor: string;\r\n private outsideOverlayColor: string;\r\n\r\n private readonly HANDLE_SIZE = 8;\r\n private readonly MIN_CROP_SIZE = this.HANDLE_SIZE * 2;\r\n\r\n // State\r\n\r\n private cropMode = false;\r\n\r\n private cropRect: Rect = { x: 0, y: 0, w: 0, h: 0 };\r\n\r\n private dragging = false;\r\n private dragOffsetX = 0;\r\n private dragOffsetY = 0;\r\n /** The handle currently being dragged, or \"rect\" for the whole rectangle. */\r\n private activeHandle: Handle | \"rect\" | null = null;\r\n private shiftPressed = false;\r\n private lastMouseX = 0;\r\n private lastMouseY = 0;\r\n\r\n // --- CONSTRUCTOR & LIFECYCLE METHODS ---\r\n\r\n /**\r\n * Creates an instance of the CropModule.\r\n * @param elements - The DOM elements used by the module.\r\n * @param options - The options for configuring the crop module.\r\n */\r\n constructor(elements: CropDOMElements, options: CropModuleOptions) {\r\n super(\"crop\");\r\n this.canvas = options.canvas;\r\n this.ctx = options.ctx;\r\n\r\n this.getCurrentImage = options.getCurrentImage;\r\n this.requestRedraw = options.requestRedraw;\r\n this.onCropAppliedCallback = options.onCropApplied;\r\n\r\n this.applyButton = elements.applyButton;\r\n this.frameColor = options.frameColor || \"red\";\r\n this.outsideOverlayColor = options.outsideOverlayColor || \"rgba(0, 0, 0, 0.2)\";\r\n }\r\n\r\n /**\r\n * Initializes the module by attaching event listeners.\r\n */\r\n public init(): void {\r\n this.addEventListener(this.applyButton, \"click\", this._applyCrop);\r\n }\r\n\r\n /**\r\n * Cleans up resources and event listeners.\r\n */\r\n public destroy(): void {\r\n this.deactivate(); // Ensure all listeners are removed\r\n super.destroy();\r\n }\r\n\r\n // --- PUBLIC API & STATE MANAGEMENT ---\r\n\r\n /**\r\n * Activates the crop module, making it ready for user interaction.\r\n */\r\n public activate(): void {\r\n this.enableCropMode();\r\n }\r\n\r\n /**\r\n * Deactivates the crop module.\r\n */\r\n public deactivate(): void {\r\n this.disableCropMode();\r\n }\r\n\r\n /**\r\n * Checks if the crop mode is currently active.\r\n * @returns True if crop mode is active, false otherwise.\r\n */\r\n public isCropModeActive(): boolean {\r\n return this.cropMode;\r\n }\r\n\r\n /**\r\n * Enables crop mode.\r\n * This initializes the crop rectangle to a default size and adds all necessary event listeners for interaction.\r\n * Enables crop mode, initializing the crop rectangle and adding event listeners.\r\n */\r\n public enableCropMode(): void {\r\n if (!this.getCurrentImage() || this.cropMode) {\r\n return;\r\n }\r\n\r\n this.cropMode = true;\r\n\r\n // Initialize crop rectangle: centered, half of canvas size\r\n const cw = this.canvas.width;\r\n const ch = this.canvas.height;\r\n this.cropRect = { x: cw / 4, y: ch / 4, w: cw / 2, h: ch / 2 };\r\n this.shiftPressed = false;\r\n\r\n // Add event listeners\r\n this.canvas.addEventListener(\"mousedown\", this._onMouseDown);\r\n this.canvas.addEventListener(\"mousemove\", this._onMouseMove);\r\n window.addEventListener(\"mouseup\", this._onMouseUp);\r\n window.addEventListener(\"keydown\", this.onKeyDown);\r\n window.addEventListener(\"keyup\", this._onKeyUp);\r\n\r\n this.requestRedraw?.();\r\n }\r\n\r\n /**\r\n * Disables crop mode.\r\n * This resets the module's state and removes all event listeners related to cropping.\r\n * Disables crop mode, resetting state and removing event listeners.\r\n */\r\n public disableCropMode(): void {\r\n if (!this.cropMode) return;\r\n\r\n this.cropMode = false;\r\n this.dragging = false;\r\n this.activeHandle = null;\r\n\r\n // Remove event listeners\r\n this.canvas.removeEventListener(\"mousedown\", this._onMouseDown);\r\n this.canvas.removeEventListener(\"mousemove\", this._onMouseMove);\r\n window.removeEventListener(\"mouseup\", this._onMouseUp);\r\n window.removeEventListener(\"keydown\", this.onKeyDown);\r\n window.removeEventListener(\"keyup\", this._onKeyUp);\r\n\r\n // Restore default cursor\r\n this.canvas.style.cursor = \"default\";\r\n\r\n this.requestRedraw?.();\r\n }\r\n\r\n // --- EVENT HANDLERS ---\r\n\r\n /**\r\n * Handles the keydown event, specifically for the Shift key to toggle aspect ratio lock.\r\n * @internal\r\n */\r\n public onKeyDown = (e: KeyboardEvent): void => {\r\n if (e.key === \"Shift\") {\r\n this.shiftPressed = true;\r\n // Redraw with aspect ratio constraint if currently dragging\r\n if (this.dragging) {\r\n this._handleDrag(this.lastMouseX, this.lastMouseY);\r\n }\r\n }\r\n };\r\n\r\n /**\r\n * Handles the keyup event, specifically for the Shift key to toggle aspect ratio lock.\r\n * @param e - The keyboard event.\r\n */\r\n private _onKeyUp = (e: KeyboardEvent): void => {\r\n if (e.key === \"Shift\" && this.cropMode) {\r\n this.shiftPressed = false;\r\n // Redraw without aspect ratio constraint if currently dragging\r\n if (this.dragging) {\r\n this._handleDrag(this.lastMouseX, this.lastMouseY);\r\n }\r\n }\r\n };\r\n\r\n /**\r\n * Handles the mousedown event to initiate dragging or resizing of the crop rectangle.\r\n * @param event - The mouse event.\r\n */\r\n private _onMouseDown = (event: MouseEvent): void => {\r\n if (!this.cropMode) return;\r\n\r\n const mx = event.offsetX;\r\n const my = event.offsetY;\r\n\r\n this.activeHandle = this._detectHandle(mx, my);\r\n\r\n // Start dragging if a handle or the rectangle itself is clicked\r\n if (this.activeHandle) {\r\n this.dragging = true;\r\n if (this.activeHandle === \"rect\") {\r\n this.dragOffsetX = mx - this.cropRect.x;\r\n this.dragOffsetY = my - this.cropRect.y;\r\n }\r\n }\r\n };\r\n\r\n /**\r\n * Handles the mousemove event to update the crop rectangle during drag/resize and to update the cursor style.\r\n * @param event - The mouse event.\r\n */\r\n private _onMouseMove = (event: MouseEvent): void => {\r\n this.lastMouseX = event.offsetX;\r\n this.lastMouseY = event.offsetY;\r\n\r\n // If not dragging, just update the cursor based on position\r\n if (!this.cropMode || !this.dragging || !this.activeHandle) {\r\n if (this.cropMode) {\r\n const handle = this._detectHandle(event.offsetX, event.offsetY);\r\n this._updateCursor(handle);\r\n }\r\n return;\r\n }\r\n // If dragging, handle the drag/resize logic\r\n this._handleDrag(this.lastMouseX, this.lastMouseY);\r\n };\r\n\r\n /**\r\n * Handles the mouseup event to finalize the drag/resize operation and normalize the crop rectangle.\r\n */\r\n private _onMouseUp = (): void => {\r\n if (!this.cropMode || !this.dragging) {\r\n return;\r\n }\r\n this.dragging = false;\r\n this.activeHandle = null;\r\n\r\n this._normalizeCropRect();\r\n\r\n this.requestRedraw?.();\r\n };\r\n\r\n // --- PRIVATE LOGIC ---\r\n\r\n /**\r\n * Handles the dragging logic for resizing and moving the crop rectangle.\r\n * @param mx - The current mouse X position.\r\n * @param my - The current mouse Y position.\r\n */\r\n private _handleDrag(mx: number, my: number): void {\r\n let { x, y, w, h } = this.cropRect;\r\n\r\n if (this.activeHandle === \"rect\") {\r\n x = mx - this.dragOffsetX;\r\n y = my - this.dragOffsetY;\r\n } else {\r\n const handle = this.activeHandle;\r\n switch (handle) {\r\n case Handle.NW:\r\n w += x - mx;\r\n h += y - my;\r\n x = mx;\r\n y = my;\r\n break;\r\n case Handle.NE:\r\n w = mx - x;\r\n h += y - my;\r\n y = my;\r\n break;\r\n case Handle.SW:\r\n w += x - mx;\r\n h = my - y;\r\n x = mx;\r\n break;\r\n case Handle.SE:\r\n w = mx - x;\r\n h = my - y;\r\n break;\r\n }\r\n\r\n if (this.shiftPressed) {\r\n const absW = Math.abs(w);\r\n const absH = Math.abs(h);\r\n const signW = w < 0 ? -1 : 1;\r\n const signH = h < 0 ? -1 : 1;\r\n\r\n if (absW > absH) {\r\n h = signH * absW;\r\n } else {\r\n w = signW * absH;\r\n }\r\n }\r\n }\r\n\r\n this.cropRect = { x, y, w, h };\r\n this.requestRedraw?.();\r\n }\r\n\r\n /**\r\n * Normalizes the crop rectangle after a drag/resize operation.\r\n * Ensures width and height are positive and clamps the rectangle to canvas boundaries.\r\n */\r\n private _normalizeCropRect(): void {\r\n // Ensure width and height are positive\r\n if (this.cropRect.w < 0) {\r\n this.cropRect.x += this.cropRect.w;\r\n this.cropRect.w = Math.abs(this.cropRect.w);\r\n }\r\n if (this.cropRect.h < 0) {\r\n this.cropRect.y += this.cropRect.h;\r\n this.cropRect.h = Math.abs(this.cropRect.h);\r\n }\r\n\r\n // Enforce minimum size\r\n if (this.cropRect.w < this.MIN_CROP_SIZE) this.cropRect.w = this.MIN_CROP_SIZE;\r\n if (this.cropRect.h < this.MIN_CROP_SIZE) this.cropRect.h = this.MIN_CROP_SIZE;\r\n\r\n // Clamp to canvas boundaries\r\n this.cropRect.x = Math.max(0, this.cropRect.x);\r\n this.cropRect.y = Math.max(0, this.cropRect.y);\r\n if (this.cropRect.x + this.cropRect.w > this.canvas.width) {\r\n this.cropRect.w = this.canvas.width - this.cropRect.x;\r\n }\r\n if (this.cropRect.y + this.cropRect.h > this.canvas.height) {\r\n this.cropRect.h = this.canvas.height - this.cropRect.y;\r\n }\r\n }\r\n\r\n /**\r\n * Applies the crop and provides the new image data URL to the callback.\r\n */\r\n private _applyCrop = (): void => {\r\n const img = this.getCurrentImage();\r\n if (!img || !this.cropMode || this.cropRect.w === 0 || this.cropRect.h === 0) {\r\n this.deactivate();\r\n return;\r\n }\r\n\r\n // Use a normalized rectangle for cropping\r\n const { x, y, w, h } = this._getVisualRect();\r\n\r\n // Map canvas coordinates to the original image's coordinates.\r\n // This is crucial if the displayed image is scaled to fit the canvas.\r\n const scaleX = img.naturalWidth / this.canvas.width;\r\n const scaleY = img.naturalHeight / this.canvas.height;\r\n\r\n const sx = x * scaleX;\r\n const sy = y * scaleY;\r\n const sw = w * scaleX;\r\n const sh = h * scaleY;\r\n\r\n if (sw <= 0 || sh <= 0) {\r\n console.error(\"Crop dimensions are invalid for applying crop.\");\r\n this.deactivate();\r\n return;\r\n }\r\n\r\n // Create an off-screen canvas to draw the cropped image\r\n const offscreenCanvas = document.createElement(\"canvas\");\r\n offscreenCanvas.width = sw;\r\n offscreenCanvas.height = sh;\r\n const offCtx = offscreenCanvas.getContext(\"2d\");\r\n\r\n if (!offCtx) {\r\n console.error(\"Failed to get 2D context for offscreen canvas\");\r\n return;\r\n }\r\n\r\n // Draw the cropped portion of the original image onto the off-screen canvas\r\n offCtx.drawImage(img, sx, sy, sw, sh, 0, 0, sw, sh);\r\n\r\n // Pass the new image data URL to the callback\r\n this.onCropAppliedCallback(offscreenCanvas.toDataURL());\r\n\r\n // The main application will handle deactivating the module after the new image is loaded.\r\n };\r\n\r\n // --- DRAWING & HELPER METHODS ---\r\n\r\n /**\r\n * Draws the crop overlay, including the semi-transparent mask, border, and handles.\r\n */\r\n public drawOverlay(): void {\r\n if (!this.cropMode) {\r\n return;\r\n }\r\n\r\n const { x, y, w, h } = this._getVisualRect();\r\n\r\n this.ctx.save();\r\n\r\n // Draw the dimmed background using the \"evenodd\" rule\r\n this.ctx.fillStyle = this.outsideOverlayColor;\r\n this.ctx.beginPath();\r\n this.ctx.rect(0, 0, this.canvas.width, this.canvas.height); // Entire area\r\n this.ctx.rect(x, y, w, h); // Cutout (crop)\r\n this.ctx.fill(\"evenodd\"); // Fill with cutout\r\n\r\n // Draw the crop rectangle border\r\n this.ctx.strokeStyle = this.frameColor;\r\n this.ctx.lineWidth = 2;\r\n this.ctx.strokeRect(x, y, w, h);\r\n\r\n // Draw the handles\r\n this.ctx.fillStyle = this.frameColor;\r\n for (const handle of Object.values(Handle)) {\r\n const { x: hx, y: hy } = this._getVisualHandleCoord(handle);\r\n this.ctx.fillRect(hx - this.HANDLE_SIZE / 2, hy - this.HANDLE_SIZE / 2, this.HANDLE_SIZE, this.HANDLE_SIZE);\r\n }\r\n\r\n this.ctx.restore();\r\n }\r\n\r\n /**\r\n * Detects if the mouse is over a handle or the crop rectangle itself.\r\n * @param mx - The mouse X position.\r\n * @param my - The mouse Y position.\r\n * @returns The active handle, \"rect\" if inside the rectangle, otherwise null.\r\n */\r\n private _detectHandle(mx: number, my: number): Handle | \"rect\" | null {\r\n // Check handles first\r\n for (const handle of Object.values(Handle)) {\r\n const { x, y } = this._getVisualHandleCoord(handle);\r\n if (Math.abs(mx - x) <= this.HANDLE_SIZE && Math.abs(my - y) <= this.HANDLE_SIZE) {\r\n return handle;\r\n }\r\n }\r\n\r\n // Then check if inside the rectangle (but not on a handle)\r\n const { x, y, w, h } = this._getVisualRect();\r\n if (mx >= x && mx <= x + w && my >= y && my <= y + h) {\r\n return \"rect\";\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * Gets the visual representation of the crop rectangle, ensuring positive width and height.\r\n * @returns A rectangle with positive dimensions for drawing and hit detection.\r\n */\r\n private _getVisualRect(): Rect {\r\n const { x, y, w, h } = this.cropRect;\r\n return {\r\n x: w > 0 ? x : x + w,\r\n y: h > 0 ? y : y + h,\r\n w: Math.abs(w),\r\n h: Math.abs(h),\r\n };\r\n }\r\n\r\n /**\r\n * Calculates the visual coordinates of a specific resize handle.\r\n * @param handle - The handle to get coordinates for.\r\n * @returns The visual coordinates of the handle.\r\n */\r\n private _getVisualHandleCoord(handle: Handle): { x: number; y: number } {\r\n const { x, y, w, h } = this._getVisualRect();\r\n\r\n switch (handle) {\r\n case Handle.NW:\r\n return { x, y };\r\n case Handle.NE:\r\n return { x: x + w, y };\r\n case Handle.SW:\r\n return { x, y: y + h };\r\n case Handle.SE:\r\n return { x: x + w, y: y + h };\r\n }\r\n }\r\n\r\n /**\r\n * Updates the canvas cursor style based on the handle being hovered over.\r\n * @param handle - The handle currently under the cursor.\r\n */\r\n private _updateCursor(handle: Handle | \"rect\" | null): void {\r\n if (handle === \"rect\") {\r\n this.canvas.style.cursor = \"move\";\r\n } else if (handle) {\r\n this.canvas.style.cursor = \"crosshair\";\r\n } else {\r\n this.canvas.style.cursor = \"default\";\r\n }\r\n }\r\n}\r\n"],"names":["Handle","CropModule","BaseModule","elements","options","cw","ch","e","event","mx","my","handle","x","y","w","h","absW","absH","signW","signH","img","scaleX","scaleY","sx","sy","sw","sh","offscreenCanvas","offCtx","hx","hy"],"mappings":"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"}
1
+ {"version":3,"file":"CropModule.js","sources":["../../src/modules/CropModule.ts"],"sourcesContent":["import type { CropDOMElements } from \"../utils/dom-manager.js\";\r\nimport { CanvasState, CropHandle, CropRect } from \"../state/CanvasState.js\";\r\nimport { BaseModule } from \"./BaseModule.js\";\r\n\r\n// --- TYPE DEFINITIONS ---\r\n\r\n/**\r\n * Configuration options for the CropModule.\r\n */\r\nexport interface CropModuleOptions {\r\n canvas: HTMLCanvasElement;\r\n ctx: CanvasRenderingContext2D;\r\n state: CanvasState;\r\n onCropApplied: () => void;\r\n frameColor?: string;\r\n outsideOverlayColor?: string;\r\n}\r\n\r\n/**\r\n * Enum representing the four resize handles of the crop rectangle.\r\n */\r\nenum Handle {\r\n NW = \"nw\",\r\n NE = \"ne\",\r\n SW = \"sw\",\r\n SE = \"se\",\r\n}\r\n\r\n/**\r\n * A module for cropping images on the canvas. It provides a draggable and resizable\r\n * rectangle to define the crop area.\r\n */\r\n\r\nexport class CropModule extends BaseModule {\r\n // --- PROPERTIES ---\r\n\r\n /** Core Dependencies */\r\n private readonly canvas: HTMLCanvasElement;\r\n\r\n private readonly ctx: CanvasRenderingContext2D;\r\n\r\n private readonly applyButton: HTMLElement;\r\n\r\n private readonly state: CanvasState;\r\n\r\n private readonly onCropAppliedCallback: () => void;\r\n\r\n // Configuration\r\n private readonly frameColor: string;\r\n private readonly outsideOverlayColor: string;\r\n\r\n private readonly HANDLE_SIZE = 8;\r\n private readonly MIN_CROP_SIZE = this.HANDLE_SIZE * 2;\r\n\r\n private isLocalActive = false;\r\n private dragging = false;\r\n private dragOffsetX = 0;\r\n private dragOffsetY = 0;\r\n private activeHandle: CropHandle = null;\r\n private shiftPressed = false;\r\n private lastMouseX = 0;\r\n private lastMouseY = 0;\r\n private previousTouchAction = \"\";\r\n\r\n // --- CONSTRUCTOR & LIFECYCLE METHODS ---\r\n\r\n /**\r\n * Creates an instance of the CropModule.\r\n * @param elements - The DOM elements used by the module.\r\n * @param options - The options for configuring the crop module.\r\n */\r\n constructor(elements: CropDOMElements, options: CropModuleOptions) {\r\n super(\"crop\");\r\n this.canvas = options.canvas;\r\n this.ctx = options.ctx;\r\n\r\n this.state = options.state;\r\n this.onCropAppliedCallback = options.onCropApplied;\r\n\r\n this.applyButton = elements.applyButton;\r\n this.frameColor = options.frameColor || \"red\";\r\n this.outsideOverlayColor = options.outsideOverlayColor || \"rgba(0, 0, 0, 0.2)\";\r\n }\r\n\r\n /**\r\n * Initializes the module by attaching event listeners.\r\n */\r\n public init(): void {\r\n this.addEventListener(this.applyButton, \"click\", this._applyCrop);\r\n this.addEventListener(this.canvas, \"mousedown\", this._onMouseDown);\r\n this.addEventListener(this.canvas, \"mousemove\", this._onMouseMove);\r\n this.addEventListener(globalThis, \"mouseup\", this._onMouseUp);\r\n this.addEventListener(globalThis, \"keydown\", this._onKeyDown);\r\n this.addEventListener(globalThis, \"keyup\", this._onKeyUp);\r\n this.addEventListener(this.canvas, \"touchstart\", this._onTouchStart, { passive: false });\r\n this.addEventListener(this.canvas, \"touchmove\", this._onTouchMove, { passive: false });\r\n this.addEventListener(globalThis, \"touchend\", this._onTouchEnd, { passive: false });\r\n this.addEventListener(globalThis, \"touchcancel\", this._onTouchCancel, { passive: false });\r\n }\r\n\r\n /**\r\n * Cleans up resources and event listeners.\r\n */\r\n public destroy(): void {\r\n this.deactivate(); // Reset crop state before final listener cleanup\r\n super.destroy();\r\n }\r\n\r\n // --- PUBLIC API & STATE MANAGEMENT ---\r\n\r\n /**\r\n * Activates the crop module, making it ready for user interaction.\r\n */\r\n public activate(): void {\r\n this._enableCropMode();\r\n }\r\n\r\n /**\r\n * Deactivates the crop module.\r\n */\r\n public deactivate(): void {\r\n this._disableCropMode();\r\n }\r\n\r\n /**\r\n * Enables crop mode.\r\n * This initializes the crop rectangle to a default size.\r\n */\r\n private _enableCropMode(): void {\r\n // Prevent double activation which causes event listener leaks and crashes\r\n if (this.isLocalActive || !this.state.getCurrent()) {\r\n return;\r\n }\r\n this.isLocalActive = true;\r\n this.dragging = false;\r\n this.dragOffsetX = 0;\r\n this.dragOffsetY = 0;\r\n this.activeHandle = null;\r\n this.shiftPressed = false;\r\n this.lastMouseX = 0;\r\n this.lastMouseY = 0;\r\n this.previousTouchAction = this.canvas.style.touchAction;\r\n this.canvas.style.touchAction = \"none\";\r\n\r\n // Always reset crop rectangle to fit the CURRENT canvas size (with a small margin).\r\n // We do not restore previous rects because the image might have been transformed (rotated/resized).\r\n const cw = this.canvas.width;\r\n const ch = this.canvas.height;\r\n\r\n // Use 60% of the smaller dimension to ensure it fits safely and is a square\r\n const size = Math.min(cw, ch) * 0.6;\r\n\r\n this.state.setCropState({\r\n active: true,\r\n rect: { x: (cw - size) / 2, y: (ch - size) / 2, w: size, h: size },\r\n });\r\n }\r\n\r\n /**\r\n * Disables crop mode.\r\n * This resets the module's state.\r\n */\r\n private _disableCropMode(): void {\r\n if (!this.isLocalActive && !this.state.getCropState().active) return;\r\n\r\n this.isLocalActive = false;\r\n this.dragging = false;\r\n this.dragOffsetX = 0;\r\n this.dragOffsetY = 0;\r\n this.activeHandle = null;\r\n this.shiftPressed = false;\r\n this.lastMouseX = 0;\r\n this.lastMouseY = 0;\r\n this.canvas.style.touchAction = this.previousTouchAction;\r\n const rect = this._getCropRect();\r\n this.state.setCropState({ active: false, rect: { ...rect } });\r\n\r\n // Restore default cursor\r\n this.canvas.style.cursor = \"default\";\r\n }\r\n\r\n // --- EVENT HANDLERS ---\r\n\r\n /**\r\n * Handles the keydown event, specifically for the Shift key to toggle aspect ratio lock.\r\n * @internal\r\n */\r\n private readonly _onKeyDown = (e: KeyboardEvent): void => {\r\n if (!this.state.getCropState().active) {\r\n return;\r\n }\r\n if (e.key === \"Shift\") {\r\n this.shiftPressed = true;\r\n // Recalculate current drag with square constraint.\r\n if (this.dragging) {\r\n this._handleDrag(this.lastMouseX, this.lastMouseY);\r\n }\r\n }\r\n };\r\n\r\n /**\r\n * Handles the keyup event, specifically for the Shift key to toggle aspect ratio lock.\r\n * @param e - The keyboard event.\r\n */\r\n private readonly _onKeyUp = (e: KeyboardEvent): void => {\r\n if (e.key === \"Shift\" && this.state.getCropState().active) {\r\n this.shiftPressed = false;\r\n // Recalculate current drag without square constraint.\r\n if (this.dragging) {\r\n this._handleDrag(this.lastMouseX, this.lastMouseY);\r\n }\r\n }\r\n };\r\n\r\n /**\r\n * Handles the mousedown event to initiate dragging or resizing of the crop rectangle.\r\n * @param event - The mouse event.\r\n */\r\n private readonly _onMouseDown = (event: MouseEvent): void => {\r\n const cropState = this.state.getCropState();\r\n if (!cropState.active) return;\r\n\r\n const mx = event.offsetX;\r\n const my = event.offsetY;\r\n\r\n const activeHandle = this._detectHandle(mx, my);\r\n\r\n // Start dragging if a handle or the rectangle itself is clicked\r\n if (activeHandle) {\r\n const rect = this._getCropRect();\r\n this.dragging = true;\r\n this.activeHandle = activeHandle;\r\n this.dragOffsetX = activeHandle === \"rect\" ? mx - rect.x : 0;\r\n this.dragOffsetY = activeHandle === \"rect\" ? my - rect.y : 0;\r\n }\r\n };\r\n\r\n /**\r\n * Handles the mousemove event to update the crop rectangle during drag/resize and to update the cursor style.\r\n * @param event - The mouse event.\r\n */\r\n private readonly _onMouseMove = (event: MouseEvent): void => {\r\n const cropState = this.state.getCropState();\r\n if (!cropState.active) {\r\n return;\r\n }\r\n this.lastMouseX = event.offsetX;\r\n this.lastMouseY = event.offsetY;\r\n\r\n // If not dragging, just update the cursor based on position\r\n if (!this.dragging || !this.activeHandle) {\r\n const handle = this._detectHandle(event.offsetX, event.offsetY);\r\n this._updateCursor(handle);\r\n return;\r\n }\r\n // If dragging, handle the drag/resize logic\r\n this._handleDrag(this.lastMouseX, this.lastMouseY);\r\n };\r\n\r\n /**\r\n * Handles the mouseup event to finalize the drag/resize operation and normalize the crop rectangle.\r\n */\r\n private readonly _onMouseUp = (): void => {\r\n const cropState = this.state.getCropState();\r\n if (!cropState.active || !this.dragging) {\r\n return;\r\n }\r\n this.dragging = false;\r\n this.activeHandle = null;\r\n\r\n this._normalizeCropRect();\r\n };\r\n\r\n private readonly _onTouchStart = (event: TouchEvent): void => {\r\n const cropState = this.state.getCropState();\r\n if (!cropState.active) {\r\n return;\r\n }\r\n\r\n const point = this._getTouchPoint(event);\r\n if (!point) {\r\n return;\r\n }\r\n event.preventDefault();\r\n\r\n const activeHandle = this._detectHandle(point.x, point.y);\r\n if (activeHandle) {\r\n const rect = this._getCropRect();\r\n this.dragging = true;\r\n this.activeHandle = activeHandle;\r\n this.dragOffsetX = activeHandle === \"rect\" ? point.x - rect.x : 0;\r\n this.dragOffsetY = activeHandle === \"rect\" ? point.y - rect.y : 0;\r\n this.lastMouseX = point.x;\r\n this.lastMouseY = point.y;\r\n }\r\n };\r\n\r\n private readonly _onTouchMove = (event: TouchEvent): void => {\r\n const cropState = this.state.getCropState();\r\n if (!cropState.active) {\r\n return;\r\n }\r\n\r\n const point = this._getTouchPoint(event);\r\n if (!point) {\r\n return;\r\n }\r\n event.preventDefault();\r\n\r\n this.lastMouseX = point.x;\r\n this.lastMouseY = point.y;\r\n\r\n if (!this.dragging || !this.activeHandle) {\r\n return;\r\n }\r\n this._handleDrag(this.lastMouseX, this.lastMouseY);\r\n };\r\n\r\n private readonly _onTouchEnd = (event: TouchEvent): void => {\r\n const cropState = this.state.getCropState();\r\n if (!cropState.active || !this.dragging) {\r\n return;\r\n }\r\n event.preventDefault();\r\n this.dragging = false;\r\n this.activeHandle = null;\r\n\r\n this._normalizeCropRect();\r\n };\r\n\r\n private readonly _onTouchCancel = (event: TouchEvent): void => {\r\n const cropState = this.state.getCropState();\r\n if (!cropState.active || !this.dragging) {\r\n return;\r\n }\r\n event.preventDefault();\r\n this.dragging = false;\r\n this.activeHandle = null;\r\n\r\n this._normalizeCropRect();\r\n };\r\n\r\n // --- PRIVATE LOGIC ---\r\n\r\n /**\r\n * Handles the dragging logic for resizing and moving the crop rectangle.\r\n * @param mx - The current mouse X position.\r\n * @param my - The current mouse Y position.\r\n */\r\n private _handleDrag(mx: number, my: number): void {\r\n if (!this.activeHandle) {\r\n return;\r\n }\r\n const nextRect = this.activeHandle === \"rect\"\r\n ? this._moveRect(this._getCropRect(), mx, my, this.dragOffsetX, this.dragOffsetY)\r\n : this._resizeRect(this._getCropRect(), mx, my, this.activeHandle as Handle, this.shiftPressed);\r\n\r\n this._setCropRect(nextRect);\r\n }\r\n\r\n /**\r\n * Normalizes the crop rectangle after a drag/resize operation.\r\n * Ensures width and height are positive and clamps the rectangle to canvas boundaries.\r\n */\r\n private _normalizeCropRect(): void {\r\n const cropRect = { ...this._getCropRect() };\r\n const canvasWidth = Math.max(1, this.canvas.width);\r\n const canvasHeight = Math.max(1, this.canvas.height);\r\n const minWidth = Math.min(this.MIN_CROP_SIZE, canvasWidth);\r\n const minHeight = Math.min(this.MIN_CROP_SIZE, canvasHeight);\r\n const clamp = (value: number, min: number, max: number): number => Math.min(Math.max(value, min), max);\r\n\r\n // Ensure width and height are positive\r\n if (cropRect.w < 0) {\r\n cropRect.x += cropRect.w;\r\n cropRect.w = Math.abs(cropRect.w);\r\n }\r\n if (cropRect.h < 0) {\r\n cropRect.y += cropRect.h;\r\n cropRect.h = Math.abs(cropRect.h);\r\n }\r\n\r\n // Clamp position first so minimum size can still be enforced near canvas edges.\r\n cropRect.x = clamp(cropRect.x, 0, canvasWidth - minWidth);\r\n cropRect.y = clamp(cropRect.y, 0, canvasHeight - minHeight);\r\n\r\n // Then clamp dimensions to [min, available-space].\r\n const maxWidth = canvasWidth - cropRect.x;\r\n const maxHeight = canvasHeight - cropRect.y;\r\n cropRect.w = clamp(cropRect.w, minWidth, maxWidth);\r\n cropRect.h = clamp(cropRect.h, minHeight, maxHeight);\r\n\r\n this._setCropRect(cropRect);\r\n }\r\n\r\n /**\r\n * Applies the crop and provides the new image data URL to the callback.\r\n */\r\n private readonly _applyCrop = (): void => {\r\n const rect = this._getCropRect();\r\n if (!this.state.getCropState().active || rect.w === 0 || rect.h === 0) {\r\n this.deactivate();\r\n return;\r\n }\r\n\r\n // Notify the editor to apply the crop based on the current state\r\n this.onCropAppliedCallback();\r\n };\r\n\r\n // --- DRAWING & HELPER METHODS ---\r\n\r\n /**\r\n * Draws the crop overlay, including the semi-transparent mask, border, and handles.\r\n */\r\n public drawOverlay(): void {\r\n if (!this.state.getCropState().active) {\r\n return;\r\n }\r\n\r\n const { x, y, w, h } = this._getVisualRect();\r\n\r\n this.ctx.save();\r\n\r\n // Draw the dimmed background using the \"evenodd\" rule\r\n this.ctx.fillStyle = this.outsideOverlayColor;\r\n this.ctx.beginPath();\r\n this.ctx.rect(0, 0, this.canvas.width, this.canvas.height); // Entire area\r\n this.ctx.rect(x, y, w, h); // Cutout (crop)\r\n this.ctx.fill(\"evenodd\"); // Fill with cutout\r\n\r\n // Draw the crop rectangle border\r\n this.ctx.strokeStyle = this.frameColor;\r\n this.ctx.lineWidth = 2;\r\n this.ctx.strokeRect(x, y, w, h);\r\n\r\n // Draw the handles\r\n this.ctx.fillStyle = this.frameColor;\r\n for (const handle of Object.values(Handle)) {\r\n const { x: hx, y: hy } = this._getVisualHandleCoord(handle);\r\n this.ctx.fillRect(hx - this.HANDLE_SIZE / 2, hy - this.HANDLE_SIZE / 2, this.HANDLE_SIZE, this.HANDLE_SIZE);\r\n }\r\n\r\n this.ctx.restore();\r\n }\r\n\r\n /**\r\n * Detects if the mouse is over a handle or the crop rectangle itself.\r\n * @param mx - The mouse X position.\r\n * @param my - The mouse Y position.\r\n * @returns The active handle, \"rect\" if inside the rectangle, otherwise null.\r\n */\r\n private _detectHandle(mx: number, my: number): CropHandle {\r\n // Check handles first\r\n for (const handle of Object.values(Handle)) {\r\n const { x, y } = this._getVisualHandleCoord(handle);\r\n if (Math.abs(mx - x) <= this.HANDLE_SIZE && Math.abs(my - y) <= this.HANDLE_SIZE) {\r\n return handle;\r\n }\r\n }\r\n\r\n // Then check if inside the rectangle (but not on a handle)\r\n const { x, y, w, h } = this._getVisualRect();\r\n if (mx >= x && mx <= x + w && my >= y && my <= y + h) {\r\n return \"rect\";\r\n }\r\n\r\n return null;\r\n }\r\n\r\n /**\r\n * Gets the visual representation of the crop rectangle, ensuring positive width and height.\r\n * @returns A rectangle with positive dimensions for drawing and hit detection.\r\n */\r\n private _getVisualRect(): CropRect {\r\n const { x, y, w, h } = this._getCropRect();\r\n return {\r\n x: w > 0 ? x : x + w,\r\n y: h > 0 ? y : y + h,\r\n w: Math.abs(w),\r\n h: Math.abs(h),\r\n };\r\n }\r\n\r\n /**\r\n * Calculates the visual coordinates of a specific resize handle.\r\n * @param handle - The handle to get coordinates for.\r\n * @returns The visual coordinates of the handle.\r\n */\r\n private _getVisualHandleCoord(handle: Handle): { x: number; y: number } {\r\n const { x, y, w, h } = this._getVisualRect();\r\n\r\n switch (handle) {\r\n case Handle.NW:\r\n return { x, y };\r\n case Handle.NE:\r\n return { x: x + w, y };\r\n case Handle.SW:\r\n return { x, y: y + h };\r\n case Handle.SE:\r\n return { x: x + w, y: y + h };\r\n }\r\n }\r\n\r\n /**\r\n * Updates the canvas cursor style based on the handle being hovered over.\r\n * @param handle - The handle currently under the cursor.\r\n */\r\n private _updateCursor(handle: CropHandle): void {\r\n if (handle === \"rect\") {\r\n this.canvas.style.cursor = \"move\";\r\n } else if (handle) {\r\n this.canvas.style.cursor = \"crosshair\";\r\n } else {\r\n this.canvas.style.cursor = \"default\";\r\n }\r\n }\r\n\r\n private _getCropRect(): CropRect {\r\n return this.state.getCropState().rect;\r\n }\r\n\r\n private _setCropRect(rect: CropRect): void {\r\n this.state.setCropState({ rect });\r\n }\r\n\r\n private _getTouchPoint(event: TouchEvent): { x: number; y: number } | null {\r\n const touch = event.touches[0] || event.changedTouches[0];\r\n if (!touch) {\r\n return null;\r\n }\r\n\r\n const bounds = this.canvas.getBoundingClientRect();\r\n if (bounds.width <= 0 || bounds.height <= 0) {\r\n return null;\r\n }\r\n\r\n const scaleX = this.canvas.width / bounds.width;\r\n const scaleY = this.canvas.height / bounds.height;\r\n return {\r\n x: (touch.clientX - bounds.left) * scaleX,\r\n y: (touch.clientY - bounds.top) * scaleY,\r\n };\r\n }\r\n\r\n private _moveRect(rect: CropRect, mx: number, my: number, dragOffsetX: number, dragOffsetY: number): CropRect {\r\n return {\r\n ...rect,\r\n x: mx - dragOffsetX,\r\n y: my - dragOffsetY,\r\n };\r\n }\r\n\r\n private _resizeRect(rect: CropRect, mx: number, my: number, handle: Handle, keepSquare: boolean): CropRect {\r\n let { x, y, w, h } = rect;\r\n\r\n switch (handle) {\r\n case Handle.NW:\r\n w += x - mx;\r\n h += y - my;\r\n x = mx;\r\n y = my;\r\n break;\r\n case Handle.NE:\r\n w = mx - x;\r\n h += y - my;\r\n y = my;\r\n break;\r\n case Handle.SW:\r\n w += x - mx;\r\n h = my - y;\r\n x = mx;\r\n break;\r\n case Handle.SE:\r\n w = mx - x;\r\n h = my - y;\r\n break;\r\n }\r\n\r\n return keepSquare ? this._asSquareRect(x, y, w, h) : { x, y, w, h };\r\n }\r\n\r\n private _asSquareRect(x: number, y: number, w: number, h: number): CropRect {\r\n const absW = Math.abs(w);\r\n const absH = Math.abs(h);\r\n const signW = w < 0 ? -1 : 1;\r\n const signH = h < 0 ? -1 : 1;\r\n\r\n if (absW > absH) {\r\n h = signH * absW;\r\n } else {\r\n w = signW * absH;\r\n }\r\n\r\n return { x, y, w, h };\r\n }\r\n}\r\n"],"names":["Handle","CropModule","BaseModule","elements","options","cw","ch","size","rect","e","event","mx","my","activeHandle","handle","point","nextRect","cropRect","canvasWidth","canvasHeight","minWidth","minHeight","clamp","value","min","max","maxWidth","maxHeight","x","y","w","h","hx","hy","touch","bounds","scaleX","scaleY","dragOffsetX","dragOffsetY","keepSquare","absW","absH","signW","signH"],"mappings":"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"}
@@ -1,31 +1,24 @@
1
+ import { ErrorHandler } from './../utils/error-handler.js';
1
2
  import { ResizeDOMElements } from '../utils/dom-manager.js';
3
+ import { CanvasState } from '../state/CanvasState.js';
2
4
  import { BaseModule } from './BaseModule.js';
3
5
  /**
4
6
  * Configuration options for the ResizeModule.
5
7
  */
6
8
  export interface ResizeModuleOptions {
7
- container: HTMLElement;
8
- canvas: HTMLCanvasElement;
9
- ctx: CanvasRenderingContext2D;
10
- getCurrentImage: () => HTMLImageElement | undefined;
11
- onRequestRedraw?: () => void;
12
- onCanvasResized?: (scaleX: number, scaleY: number) => void;
9
+ state: CanvasState;
10
+ errorHandler: ErrorHandler;
13
11
  }
14
12
  /**
15
- * A module responsible for handling image and canvas resizing based on user input.
16
- * It maintains aspect ratio if requested and ensures the canvas fits within its container.
13
+ * A module responsible for handling output image dimensions based on user input.
14
+ * The editor computes preview canvas size separately from this module.
17
15
  */
18
16
  export declare class ResizeModule extends BaseModule {
19
- private widthInput;
20
- private heightInput;
21
- private lockAspectRatio?;
22
- private container;
23
- private canvas;
24
- private ctx;
25
- private getCurrentImage;
26
- private onRequestRedraw?;
27
- private onCanvasResized?;
28
- private errorHandler;
17
+ private readonly widthInput;
18
+ private readonly heightInput;
19
+ private readonly lockAspectRatio?;
20
+ private readonly state;
21
+ private readonly errorHandler;
29
22
  private isActive;
30
23
  /**
31
24
  * Creates an instance of the ResizeModule.
@@ -46,16 +39,9 @@ export declare class ResizeModule extends BaseModule {
46
39
  */
47
40
  deactivate(): void;
48
41
  /**
49
- * Central function to read inputs, calculate dimensions, and update the canvas.
42
+ * Central function to read inputs, calculate dimensions, and update output state.
50
43
  * @param changedDim - The dimension ('width' or 'height') that initiated the change. If undefined, it's assumed the change came from the aspect ratio toggle.
51
44
  */
52
45
  private _updateCanvasAndInputs;
53
- /**
54
- * Handles the actual resizing and redrawing of the canvas.
55
- * @param img - The image to be redrawn.
56
- * @param newWidth - The target width for the image.
57
- * @param newHeight - The target height for the image.
58
- */
59
- private _redrawCanvas;
60
46
  }
61
47
  //# sourceMappingURL=ResizeModule.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ResizeModule.d.ts","sourceRoot":"","sources":["../../src/modules/ResizeModule.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAChC,SAAS,EAAE,WAAW,CAAC;IACvB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,GAAG,EAAE,wBAAwB,CAAC;IAC9B,eAAe,EAAE,MAAM,gBAAgB,GAAG,SAAS,CAAC;IACpD,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9D;AAED;;;GAGG;AACH,qBAAa,YAAa,SAAQ,UAAU;IAGxC,OAAO,CAAC,UAAU,CAAmB;IACrC,OAAO,CAAC,WAAW,CAAmB;IACtC,OAAO,CAAC,eAAe,CAAC,CAAmB;IAE3C,OAAO,CAAC,SAAS,CAAc;IAC/B,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,GAAG,CAA2B;IACtC,OAAO,CAAC,eAAe,CAAqC;IAC5D,OAAO,CAAC,eAAe,CAAC,CAAa;IACrC,OAAO,CAAC,eAAe,CAAC,CAA2C;IAEnE,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAkB;IAElC;;;;OAIG;gBACS,QAAQ,EAAE,iBAAiB,EAAE,OAAO,EAAE,mBAAmB;IAgBrE;;OAEG;IACI,IAAI,IAAI,IAAI;IAQnB;;OAEG;IACI,QAAQ,IAAI,IAAI;IAIvB;;OAEG;IACI,UAAU,IAAI,IAAI;IAIzB;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IAyC9B;;;;;OAKG;IACH,OAAO,CAAC,aAAa;CA8BxB"}
1
+ {"version":3,"file":"ResizeModule.d.ts","sourceRoot":"","sources":["../../src/modules/ResizeModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAChC,KAAK,EAAE,WAAW,CAAC;IACnB,YAAY,EAAE,YAAY,CAAC;CAC9B;AAED;;;GAGG;AACH,qBAAa,YAAa,SAAQ,UAAU;IAGxC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAmB;IAC9C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAmB;IAC/C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAmB;IAEpD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;IAEpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAS;IAEzB;;;;OAIG;gBACS,QAAQ,EAAE,iBAAiB,EAAE,OAAO,EAAE,mBAAmB;IAUrE;;OAEG;IACI,IAAI,IAAI,IAAI;IAQnB;;OAEG;IACI,QAAQ,IAAI,IAAI;IAmBvB;;OAEG;IACI,UAAU,IAAI,IAAI;IAIzB;;;OAGG;IACH,OAAO,CAAC,sBAAsB;CA+CjC"}