@bensitu/image-editor 1.5.2 → 2.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 (166) hide show
  1. package/README.md +460 -509
  2. package/dist/cjs/index.cjs +6892 -0
  3. package/dist/cjs/index.cjs.map +1 -0
  4. package/dist/esm/animation/animation-queue.js +74 -0
  5. package/dist/esm/animation/animation-queue.js.map +1 -0
  6. package/dist/esm/core/callback-reporter.js +23 -0
  7. package/dist/esm/core/callback-reporter.js.map +1 -0
  8. package/dist/esm/core/default-options.js +529 -0
  9. package/dist/esm/core/default-options.js.map +1 -0
  10. package/dist/esm/core/errors.js +156 -0
  11. package/dist/esm/core/errors.js.map +1 -0
  12. package/dist/esm/core/operation-guard.js +157 -0
  13. package/dist/esm/core/operation-guard.js.map +1 -0
  14. package/dist/esm/core/public-types.js +4 -0
  15. package/dist/esm/core/public-types.js.map +1 -0
  16. package/dist/esm/core/state-serializer.js +252 -0
  17. package/dist/esm/core/state-serializer.js.map +1 -0
  18. package/dist/esm/crop/crop-controller.js +405 -0
  19. package/dist/esm/crop/crop-controller.js.map +1 -0
  20. package/dist/esm/export/export-format.js +53 -0
  21. package/dist/esm/export/export-format.js.map +1 -0
  22. package/dist/esm/export/export-service.js +607 -0
  23. package/dist/esm/export/export-service.js.map +1 -0
  24. package/dist/esm/fabric/fabric-adapter.js +37 -0
  25. package/dist/esm/fabric/fabric-adapter.js.map +1 -0
  26. package/dist/esm/fabric/fabric-animation.js +89 -0
  27. package/dist/esm/fabric/fabric-animation.js.map +1 -0
  28. package/dist/esm/history/command.js +2 -0
  29. package/dist/esm/history/command.js.map +1 -0
  30. package/dist/esm/history/history-manager.js +103 -0
  31. package/dist/esm/history/history-manager.js.map +1 -0
  32. package/dist/esm/image/image-loader.js +238 -0
  33. package/dist/esm/image/image-loader.js.map +1 -0
  34. package/dist/esm/image/image-resampler.js +60 -0
  35. package/dist/esm/image/image-resampler.js.map +1 -0
  36. package/dist/esm/image/layout-manager.js +206 -0
  37. package/dist/esm/image/layout-manager.js.map +1 -0
  38. package/dist/esm/image/transform-controller.js +132 -0
  39. package/dist/esm/image/transform-controller.js.map +1 -0
  40. package/dist/esm/image-editor.js +2076 -0
  41. package/dist/esm/image-editor.js.map +1 -0
  42. package/dist/esm/index.js +5 -0
  43. package/dist/esm/index.js.map +1 -0
  44. package/dist/esm/mask/mask-factory.js +356 -0
  45. package/dist/esm/mask/mask-factory.js.map +1 -0
  46. package/dist/esm/mask/mask-label-manager.js +120 -0
  47. package/dist/esm/mask/mask-label-manager.js.map +1 -0
  48. package/dist/esm/mask/mask-list.js +53 -0
  49. package/dist/esm/mask/mask-list.js.map +1 -0
  50. package/dist/esm/mask/mask-style.js +182 -0
  51. package/dist/esm/mask/mask-style.js.map +1 -0
  52. package/dist/esm/mosaic/mosaic-controller.js +670 -0
  53. package/dist/esm/mosaic/mosaic-controller.js.map +1 -0
  54. package/dist/esm/mosaic/mosaic-geometry.js +81 -0
  55. package/dist/esm/mosaic/mosaic-geometry.js.map +1 -0
  56. package/dist/esm/mosaic/mosaic-pixelate.js +71 -0
  57. package/dist/esm/mosaic/mosaic-pixelate.js.map +1 -0
  58. package/dist/esm/ui/dom-bindings.js +67 -0
  59. package/dist/esm/ui/dom-bindings.js.map +1 -0
  60. package/dist/esm/ui/ui-state.js +25 -0
  61. package/dist/esm/ui/ui-state.js.map +1 -0
  62. package/dist/esm/ui/visibility-state.js +11 -0
  63. package/dist/esm/ui/visibility-state.js.map +1 -0
  64. package/dist/esm/utils/canvas-region.js +100 -0
  65. package/dist/esm/utils/canvas-region.js.map +1 -0
  66. package/dist/esm/utils/dom.js +6 -0
  67. package/dist/esm/utils/dom.js.map +1 -0
  68. package/dist/esm/utils/file.js +53 -0
  69. package/dist/esm/utils/file.js.map +1 -0
  70. package/dist/esm/utils/number.js +24 -0
  71. package/dist/esm/utils/number.js.map +1 -0
  72. package/dist/esm/utils/timeout.js +17 -0
  73. package/dist/esm/utils/timeout.js.map +1 -0
  74. package/dist/types/animation/animation-queue.d.ts +111 -0
  75. package/dist/types/animation/animation-queue.d.ts.map +1 -0
  76. package/dist/types/core/callback-reporter.d.ts +125 -0
  77. package/dist/types/core/callback-reporter.d.ts.map +1 -0
  78. package/dist/types/core/default-options.d.ts +84 -0
  79. package/dist/types/core/default-options.d.ts.map +1 -0
  80. package/dist/types/core/errors.d.ts +142 -0
  81. package/dist/types/core/errors.d.ts.map +1 -0
  82. package/dist/types/core/operation-guard.d.ts +194 -0
  83. package/dist/types/core/operation-guard.d.ts.map +1 -0
  84. package/dist/types/core/public-types.d.ts +788 -0
  85. package/dist/types/core/public-types.d.ts.map +1 -0
  86. package/dist/types/core/state-serializer.d.ts +303 -0
  87. package/dist/types/core/state-serializer.d.ts.map +1 -0
  88. package/dist/types/crop/crop-controller.d.ts +407 -0
  89. package/dist/types/crop/crop-controller.d.ts.map +1 -0
  90. package/dist/types/export/export-format.d.ts +136 -0
  91. package/dist/types/export/export-format.d.ts.map +1 -0
  92. package/dist/types/export/export-service.d.ts +333 -0
  93. package/dist/types/export/export-service.d.ts.map +1 -0
  94. package/dist/types/fabric/fabric-adapter.d.ts +74 -0
  95. package/dist/types/fabric/fabric-adapter.d.ts.map +1 -0
  96. package/dist/types/fabric/fabric-animation.d.ts +141 -0
  97. package/dist/types/fabric/fabric-animation.d.ts.map +1 -0
  98. package/dist/types/history/command.d.ts +16 -0
  99. package/dist/types/history/command.d.ts.map +1 -0
  100. package/dist/types/history/history-manager.d.ts +129 -0
  101. package/dist/types/history/history-manager.d.ts.map +1 -0
  102. package/dist/types/image/image-loader.d.ts +263 -0
  103. package/dist/types/image/image-loader.d.ts.map +1 -0
  104. package/dist/types/image/image-resampler.d.ts +139 -0
  105. package/dist/types/image/image-resampler.d.ts.map +1 -0
  106. package/dist/types/image/layout-manager.d.ts +211 -0
  107. package/dist/types/image/layout-manager.d.ts.map +1 -0
  108. package/dist/types/image/transform-controller.d.ts +286 -0
  109. package/dist/types/image/transform-controller.d.ts.map +1 -0
  110. package/dist/types/image-editor.d.ts +661 -0
  111. package/dist/types/image-editor.d.ts.map +1 -0
  112. package/dist/types/index.d.cts +31 -0
  113. package/dist/types/index.d.cts.map +1 -0
  114. package/dist/types/index.d.ts +31 -0
  115. package/dist/types/index.d.ts.map +1 -0
  116. package/dist/types/mask/mask-factory.d.ts +212 -0
  117. package/dist/types/mask/mask-factory.d.ts.map +1 -0
  118. package/dist/types/mask/mask-label-manager.d.ts +171 -0
  119. package/dist/types/mask/mask-label-manager.d.ts.map +1 -0
  120. package/dist/types/mask/mask-list.d.ts +144 -0
  121. package/dist/types/mask/mask-list.d.ts.map +1 -0
  122. package/dist/types/mask/mask-style.d.ts +338 -0
  123. package/dist/types/mask/mask-style.d.ts.map +1 -0
  124. package/dist/types/mosaic/mosaic-controller.d.ts +82 -0
  125. package/dist/types/mosaic/mosaic-controller.d.ts.map +1 -0
  126. package/dist/types/mosaic/mosaic-geometry.d.ts +29 -0
  127. package/dist/types/mosaic/mosaic-geometry.d.ts.map +1 -0
  128. package/dist/types/mosaic/mosaic-pixelate.d.ts +23 -0
  129. package/dist/types/mosaic/mosaic-pixelate.d.ts.map +1 -0
  130. package/dist/types/ui/dom-bindings.d.ts +105 -0
  131. package/dist/types/ui/dom-bindings.d.ts.map +1 -0
  132. package/dist/types/ui/ui-state.d.ts +112 -0
  133. package/dist/types/ui/ui-state.d.ts.map +1 -0
  134. package/dist/types/ui/visibility-state.d.ts +77 -0
  135. package/dist/types/ui/visibility-state.d.ts.map +1 -0
  136. package/dist/types/utils/canvas-region.d.ts +177 -0
  137. package/dist/types/utils/canvas-region.d.ts.map +1 -0
  138. package/dist/types/utils/dom.d.ts +26 -0
  139. package/dist/types/utils/dom.d.ts.map +1 -0
  140. package/dist/types/utils/file.d.ts +80 -0
  141. package/dist/types/utils/file.d.ts.map +1 -0
  142. package/dist/types/utils/number.d.ts +131 -0
  143. package/dist/types/utils/number.d.ts.map +1 -0
  144. package/dist/types/utils/timeout.d.ts +84 -0
  145. package/dist/types/utils/timeout.d.ts.map +1 -0
  146. package/dist/umd/image-editor.umd.js +2 -0
  147. package/dist/umd/image-editor.umd.js.map +1 -0
  148. package/package.json +72 -66
  149. package/dist/image-editor.cjs +0 -4407
  150. package/dist/image-editor.cjs.map +0 -7
  151. package/dist/image-editor.esm.js +0 -4376
  152. package/dist/image-editor.esm.js.map +0 -7
  153. package/dist/image-editor.esm.min.js +0 -9
  154. package/dist/image-editor.esm.min.js.map +0 -7
  155. package/dist/image-editor.esm.min.mjs +0 -9
  156. package/dist/image-editor.esm.min.mjs.map +0 -7
  157. package/dist/image-editor.esm.mjs +0 -4376
  158. package/dist/image-editor.esm.mjs.map +0 -7
  159. package/dist/image-editor.js +0 -4373
  160. package/dist/image-editor.js.map +0 -7
  161. package/dist/image-editor.min.js +0 -9
  162. package/dist/image-editor.min.js.map +0 -7
  163. package/image-editor.d.ts +0 -271
  164. package/src/browser.js +0 -11
  165. package/src/esm.js +0 -9
  166. package/src/image-editor.js +0 -5013
@@ -0,0 +1,2076 @@
1
+ import { AnimationQueue } from './animation/animation-queue.js';
2
+ import { reportError, reportWarning } from './core/callback-reporter.js';
3
+ import { areResolvedMosaicConfigsEqual, cloneResolvedMosaicConfig, getInvalidMosaicConfigFields, isLayoutMode, mergeMosaicConfigPatch, resolveOptions, } from './core/default-options.js';
4
+ import { OperationGuard } from './core/operation-guard.js';
5
+ import { loadFromState as loadFromStateImpl, saveState as saveStateImpl, } from './core/state-serializer.js';
6
+ import { Command, HistoryManager } from './history/history-manager.js';
7
+ import { detectFabric } from './fabric/fabric-adapter.js';
8
+ import { isMaskObject } from './core/public-types.js';
9
+ import { applyCrop as applyCropImpl, cancelCrop as cancelCropImpl, enterCropMode as enterCropModeImpl, } from './crop/crop-controller.js';
10
+ import { enterMosaicMode as enterMosaicModeImpl, exitMosaicMode as exitMosaicModeImpl, updateMosaicPreview, } from './mosaic/mosaic-controller.js';
11
+ import { downloadImage as downloadImageImpl, exportImageBase64 as exportImageBase64Impl, exportImageFile as exportImageFileImpl, mergeMasks as mergeMasksImpl, } from './export/export-service.js';
12
+ import { loadImage as loadImageImpl } from './image/image-loader.js';
13
+ import { ViewportCache, applyCanvasDimensions, computeScrollableCanvasSize, measureScrollbarSize, } from './image/layout-manager.js';
14
+ import { TransformController } from './image/transform-controller.js';
15
+ import { createMask as createMaskImpl, removeAllMasks as removeAllMasksImpl, removeSelectedMask as removeSelectedMaskImpl, } from './mask/mask-factory.js';
16
+ import { createLabelForMask, hideAllMaskLabels, removeLabelForMask, showLabelForMask, syncMaskLabel, } from './mask/mask-label-manager.js';
17
+ import { renderMaskList, updateMaskListSelection } from './mask/mask-list.js';
18
+ import { applyMaskSelectedStyle, applyMaskUnselectedStyle, reattachMaskHoverHandlers, } from './mask/mask-style.js';
19
+ import { DomBindings } from './ui/dom-bindings.js';
20
+ import { setPlaceholderVisible as setPlaceholderVisibleImpl } from './ui/visibility-state.js';
21
+ import { inferImageMimeType, readFileAsDataUrl, resetFileInput } from './utils/file.js';
22
+ import { detectSourceMimeType } from './image/image-resampler.js';
23
+ const LAYOUT_EPSILON = 0.5;
24
+ const INTERNAL_OPERATION_TOKEN = Symbol('ImageEditorInternalOperation');
25
+ const INTERNAL_ALLOW_DURING_ANIMATION_QUEUE = Symbol('ImageEditorAllowDuringAnimationQueue');
26
+ const CROP_MODE_CONTROL_KEYS = [
27
+ 'scalePercentageInput',
28
+ 'rotateLeftDegreesInput',
29
+ 'rotateRightDegreesInput',
30
+ 'rotateLeftButton',
31
+ 'rotateRightButton',
32
+ 'createMaskButton',
33
+ 'removeSelectedMaskButton',
34
+ 'removeAllMasksButton',
35
+ 'mergeMasksButton',
36
+ 'downloadImageButton',
37
+ 'zoomInButton',
38
+ 'zoomOutButton',
39
+ 'resetImageTransformButton',
40
+ 'undoButton',
41
+ 'redoButton',
42
+ 'imageInput',
43
+ 'enterCropModeButton',
44
+ 'applyCropButton',
45
+ 'cancelCropButton',
46
+ 'enterMosaicModeButton',
47
+ 'exitMosaicModeButton',
48
+ 'mosaicBrushSizeInput',
49
+ 'mosaicBlockSizeInput',
50
+ ];
51
+ const CROP_MODE_ENABLED_KEYS = ['applyCropButton', 'cancelCropButton'];
52
+ const CROP_SESSION_ALLOWED_OPERATIONS = new Set(['applyCrop', 'cancelCrop']);
53
+ const MOSAIC_MODE_CONTROL_KEYS = [
54
+ 'scalePercentageInput',
55
+ 'rotateLeftDegreesInput',
56
+ 'rotateRightDegreesInput',
57
+ 'rotateLeftButton',
58
+ 'rotateRightButton',
59
+ 'createMaskButton',
60
+ 'removeSelectedMaskButton',
61
+ 'removeAllMasksButton',
62
+ 'mergeMasksButton',
63
+ 'downloadImageButton',
64
+ 'zoomInButton',
65
+ 'zoomOutButton',
66
+ 'resetImageTransformButton',
67
+ 'undoButton',
68
+ 'redoButton',
69
+ 'imageInput',
70
+ 'enterCropModeButton',
71
+ 'applyCropButton',
72
+ 'cancelCropButton',
73
+ 'enterMosaicModeButton',
74
+ 'exitMosaicModeButton',
75
+ 'mosaicBrushSizeInput',
76
+ 'mosaicBlockSizeInput',
77
+ ];
78
+ const MOSAIC_MODE_ENABLED_KEYS = [
79
+ 'exitMosaicModeButton',
80
+ 'mosaicBrushSizeInput',
81
+ 'mosaicBlockSizeInput',
82
+ ];
83
+ const MOSAIC_SESSION_ALLOWED_OPERATIONS = new Set([
84
+ 'exitMosaicMode',
85
+ 'applyMosaic',
86
+ 'setMosaicConfig',
87
+ 'resetMosaicConfig',
88
+ 'setMosaicBrushSize',
89
+ 'setMosaicBlockSize',
90
+ 'saveState',
91
+ ]);
92
+ const SCROLLBAR_SETTLE_EPSILON = 1;
93
+ const IMAGE_EDITOR_OPERATIONS = new Set([
94
+ 'init',
95
+ 'loadImage',
96
+ 'loadFromState',
97
+ 'saveState',
98
+ 'scaleImage',
99
+ 'rotateImage',
100
+ 'resetImageTransform',
101
+ 'createMask',
102
+ 'removeSelectedMask',
103
+ 'removeAllMasks',
104
+ 'mergeMasks',
105
+ 'enterCropMode',
106
+ 'applyCrop',
107
+ 'cancelCrop',
108
+ 'enterMosaicMode',
109
+ 'exitMosaicMode',
110
+ 'applyMosaic',
111
+ 'setMosaicConfig',
112
+ 'resetMosaicConfig',
113
+ 'setMosaicBrushSize',
114
+ 'setMosaicBlockSize',
115
+ 'undo',
116
+ 'redo',
117
+ 'exportImageBase64',
118
+ 'exportImageFile',
119
+ 'downloadImage',
120
+ 'dispose',
121
+ ]);
122
+ function isImageEditorOperation(value) {
123
+ return value !== null && IMAGE_EDITOR_OPERATIONS.has(value);
124
+ }
125
+ export class ImageEditor {
126
+ constructor(fabricModuleOrOptions = {}, options = {}) {
127
+ var _a;
128
+ Object.defineProperty(this, "fabricModule", {
129
+ enumerable: true,
130
+ configurable: true,
131
+ writable: true,
132
+ value: void 0
133
+ });
134
+ Object.defineProperty(this, "isFabricLoaded", {
135
+ enumerable: true,
136
+ configurable: true,
137
+ writable: true,
138
+ value: void 0
139
+ });
140
+ Object.defineProperty(this, "options", {
141
+ enumerable: true,
142
+ configurable: true,
143
+ writable: true,
144
+ value: void 0
145
+ });
146
+ Object.defineProperty(this, "currentLayoutMode", {
147
+ enumerable: true,
148
+ configurable: true,
149
+ writable: true,
150
+ value: 'expand'
151
+ });
152
+ Object.defineProperty(this, "defaultMosaicConfig", {
153
+ enumerable: true,
154
+ configurable: true,
155
+ writable: true,
156
+ value: void 0
157
+ });
158
+ Object.defineProperty(this, "currentMosaicConfig", {
159
+ enumerable: true,
160
+ configurable: true,
161
+ writable: true,
162
+ value: void 0
163
+ });
164
+ Object.defineProperty(this, "canvas", {
165
+ enumerable: true,
166
+ configurable: true,
167
+ writable: true,
168
+ value: null
169
+ });
170
+ Object.defineProperty(this, "canvasElement", {
171
+ enumerable: true,
172
+ configurable: true,
173
+ writable: true,
174
+ value: null
175
+ });
176
+ Object.defineProperty(this, "containerElement", {
177
+ enumerable: true,
178
+ configurable: true,
179
+ writable: true,
180
+ value: null
181
+ });
182
+ Object.defineProperty(this, "placeholderElement", {
183
+ enumerable: true,
184
+ configurable: true,
185
+ writable: true,
186
+ value: null
187
+ });
188
+ Object.defineProperty(this, "elements", {
189
+ enumerable: true,
190
+ configurable: true,
191
+ writable: true,
192
+ value: {}
193
+ });
194
+ Object.defineProperty(this, "elementOriginalDisabledMap", {
195
+ enumerable: true,
196
+ configurable: true,
197
+ writable: true,
198
+ value: new Map()
199
+ });
200
+ Object.defineProperty(this, "elementOriginalAriaDisabledMap", {
201
+ enumerable: true,
202
+ configurable: true,
203
+ writable: true,
204
+ value: new Map()
205
+ });
206
+ Object.defineProperty(this, "elementOriginalPointerEventsMap", {
207
+ enumerable: true,
208
+ configurable: true,
209
+ writable: true,
210
+ value: new Map()
211
+ });
212
+ Object.defineProperty(this, "originalImage", {
213
+ enumerable: true,
214
+ configurable: true,
215
+ writable: true,
216
+ value: null
217
+ });
218
+ Object.defineProperty(this, "baseImageScale", {
219
+ enumerable: true,
220
+ configurable: true,
221
+ writable: true,
222
+ value: 1
223
+ });
224
+ Object.defineProperty(this, "currentScale", {
225
+ enumerable: true,
226
+ configurable: true,
227
+ writable: true,
228
+ value: 1
229
+ });
230
+ Object.defineProperty(this, "currentRotation", {
231
+ enumerable: true,
232
+ configurable: true,
233
+ writable: true,
234
+ value: 0
235
+ });
236
+ Object.defineProperty(this, "isImageLoadedToCanvas", {
237
+ enumerable: true,
238
+ configurable: true,
239
+ writable: true,
240
+ value: false
241
+ });
242
+ Object.defineProperty(this, "currentImageMimeType", {
243
+ enumerable: true,
244
+ configurable: true,
245
+ writable: true,
246
+ value: null
247
+ });
248
+ Object.defineProperty(this, "maskCounter", {
249
+ enumerable: true,
250
+ configurable: true,
251
+ writable: true,
252
+ value: 0
253
+ });
254
+ Object.defineProperty(this, "lastMask", {
255
+ enumerable: true,
256
+ configurable: true,
257
+ writable: true,
258
+ value: null
259
+ });
260
+ Object.defineProperty(this, "lastSnapshot", {
261
+ enumerable: true,
262
+ configurable: true,
263
+ writable: true,
264
+ value: null
265
+ });
266
+ Object.defineProperty(this, "historyManager", {
267
+ enumerable: true,
268
+ configurable: true,
269
+ writable: true,
270
+ value: void 0
271
+ });
272
+ Object.defineProperty(this, "operationGuard", {
273
+ enumerable: true,
274
+ configurable: true,
275
+ writable: true,
276
+ value: void 0
277
+ });
278
+ Object.defineProperty(this, "animQueue", {
279
+ enumerable: true,
280
+ configurable: true,
281
+ writable: true,
282
+ value: void 0
283
+ });
284
+ Object.defineProperty(this, "transformController", {
285
+ enumerable: true,
286
+ configurable: true,
287
+ writable: true,
288
+ value: null
289
+ });
290
+ Object.defineProperty(this, "viewportCache", {
291
+ enumerable: true,
292
+ configurable: true,
293
+ writable: true,
294
+ value: new ViewportCache()
295
+ });
296
+ Object.defineProperty(this, "cropSession", {
297
+ enumerable: true,
298
+ configurable: true,
299
+ writable: true,
300
+ value: null
301
+ });
302
+ Object.defineProperty(this, "mosaicSession", {
303
+ enumerable: true,
304
+ configurable: true,
305
+ writable: true,
306
+ value: null
307
+ });
308
+ Object.defineProperty(this, "domBindings", {
309
+ enumerable: true,
310
+ configurable: true,
311
+ writable: true,
312
+ value: null
313
+ });
314
+ Object.defineProperty(this, "isDisposed", {
315
+ enumerable: true,
316
+ configurable: true,
317
+ writable: true,
318
+ value: false
319
+ });
320
+ Object.defineProperty(this, "shouldSuppressSaveState", {
321
+ enumerable: true,
322
+ configurable: true,
323
+ writable: true,
324
+ value: false
325
+ });
326
+ Object.defineProperty(this, "lastEmittedIsBusy", {
327
+ enumerable: true,
328
+ configurable: true,
329
+ writable: true,
330
+ value: null
331
+ });
332
+ Object.defineProperty(this, "activeStateRestoreOperation", {
333
+ enumerable: true,
334
+ configurable: true,
335
+ writable: true,
336
+ value: null
337
+ });
338
+ Object.defineProperty(this, "nextSelectionChangeContext", {
339
+ enumerable: true,
340
+ configurable: true,
341
+ writable: true,
342
+ value: null
343
+ });
344
+ const detected = detectFabric(fabricModuleOrOptions, options);
345
+ this.fabricModule = (_a = detected.fabric) !== null && _a !== void 0 ? _a : {};
346
+ this.isFabricLoaded = detected.isFabricLoaded;
347
+ this.options = resolveOptions(detected.options);
348
+ this.currentLayoutMode = this.options.layoutMode;
349
+ this.defaultMosaicConfig = this.options.defaultMosaicConfig;
350
+ this.currentMosaicConfig = cloneResolvedMosaicConfig(this.defaultMosaicConfig);
351
+ const rawDefaultLayoutMode = detected.options
352
+ .defaultLayoutMode;
353
+ if (rawDefaultLayoutMode !== undefined && !isLayoutMode(rawDefaultLayoutMode)) {
354
+ reportWarning(this.options, new TypeError(`[ImageEditor] Unsupported defaultLayoutMode ` +
355
+ `${JSON.stringify(rawDefaultLayoutMode)}. ` +
356
+ 'Expected "fit", "cover", or "expand".'), 'Invalid defaultLayoutMode fell back to "expand".');
357
+ }
358
+ this.operationGuard = new OperationGuard();
359
+ this.animQueue = new AnimationQueue();
360
+ this.historyManager = new HistoryManager(this.options.maxHistorySize);
361
+ }
362
+ init(idMap = {}) {
363
+ if (!this.isFabricLoaded) {
364
+ const globalFabric = globalThis.fabric;
365
+ if (!globalFabric ||
366
+ typeof globalFabric.Canvas !== 'function') {
367
+ return;
368
+ }
369
+ this.fabricModule = globalFabric;
370
+ this.isFabricLoaded = true;
371
+ }
372
+ if (this.isDisposed)
373
+ return;
374
+ const defaults = {
375
+ canvas: 'canvas',
376
+ canvasContainer: null,
377
+ imagePlaceholder: 'imagePlaceholder',
378
+ scalePercentageInput: 'scalePercentageInput',
379
+ rotateLeftDegreesInput: 'rotateLeftDegreesInput',
380
+ rotateRightDegreesInput: 'rotateRightDegreesInput',
381
+ rotateLeftButton: 'rotateLeftButton',
382
+ rotateRightButton: 'rotateRightButton',
383
+ createMaskButton: 'createMaskButton',
384
+ removeSelectedMaskButton: 'removeSelectedMaskButton',
385
+ removeAllMasksButton: 'removeAllMasksButton',
386
+ mergeMasksButton: 'mergeMasksButton',
387
+ downloadImageButton: 'downloadImageButton',
388
+ maskList: 'maskList',
389
+ zoomInButton: 'zoomInButton',
390
+ zoomOutButton: 'zoomOutButton',
391
+ resetImageTransformButton: 'resetImageTransformButton',
392
+ undoButton: 'undoButton',
393
+ redoButton: 'redoButton',
394
+ imageInput: 'imageInput',
395
+ enterCropModeButton: 'enterCropModeButton',
396
+ applyCropButton: 'applyCropButton',
397
+ cancelCropButton: 'cancelCropButton',
398
+ enterMosaicModeButton: 'enterMosaicModeButton',
399
+ exitMosaicModeButton: 'exitMosaicModeButton',
400
+ mosaicBrushSizeInput: 'mosaicBrushSizeInput',
401
+ mosaicBlockSizeInput: 'mosaicBlockSizeInput',
402
+ uploadArea: 'uploadArea',
403
+ };
404
+ this.elements = { ...defaults, ...idMap };
405
+ this.domBindings = new DomBindings((key) => this.elements[key], () => this.isDisposed, () => { var _a, _b; return (_b = (_a = this.canvasElement) === null || _a === void 0 ? void 0 : _a.ownerDocument) !== null && _b !== void 0 ? _b : document; });
406
+ this.initCanvas();
407
+ this.transformController = new TransformController(this.buildTransformContext());
408
+ this.bindDomEvents();
409
+ this.updateInputs();
410
+ this.updateMaskList();
411
+ this.updateUi();
412
+ if (this.options.initialImageBase64) {
413
+ void this.loadImage(this.options.initialImageBase64).catch(() => {
414
+ });
415
+ }
416
+ else {
417
+ this.updatePlaceholderStatus();
418
+ }
419
+ }
420
+ initCanvas() {
421
+ var _a;
422
+ const id = this.elements.canvas;
423
+ const canvasElement = id ? document.getElementById(id) : null;
424
+ if (!canvasElement)
425
+ throw new Error(`[ImageEditor] Canvas element not found: "${id}"`);
426
+ this.canvasElement = canvasElement;
427
+ const containerId = this.elements.canvasContainer;
428
+ if (containerId) {
429
+ this.containerElement =
430
+ (_a = document.getElementById(containerId)) !== null && _a !== void 0 ? _a : canvasElement.parentElement;
431
+ }
432
+ else {
433
+ this.containerElement = canvasElement.parentElement;
434
+ }
435
+ const placeholderId = this.elements.imagePlaceholder;
436
+ this.placeholderElement = placeholderId ? document.getElementById(placeholderId) : null;
437
+ let initialWidth = this.options.canvasWidth;
438
+ let initialHeight = this.options.canvasHeight;
439
+ if (this.containerElement) {
440
+ const containerWidth = Math.floor(this.containerElement.clientWidth);
441
+ const containerHeight = Math.floor(this.containerElement.clientHeight);
442
+ if (containerWidth > 0 && containerHeight > 0) {
443
+ initialWidth = containerWidth;
444
+ initialHeight = containerHeight;
445
+ }
446
+ }
447
+ this.canvas = new this.fabricModule.Canvas(canvasElement, {
448
+ width: initialWidth,
449
+ height: initialHeight,
450
+ backgroundColor: this.options.backgroundColor,
451
+ selection: this.options.groupSelection,
452
+ preserveObjectStacking: true,
453
+ });
454
+ this.canvas.on('selection:created', (e) => {
455
+ this.handleSelectionChanged(e.selected);
456
+ });
457
+ this.canvas.on('selection:updated', (e) => {
458
+ this.handleSelectionChanged(e.selected);
459
+ });
460
+ this.canvas.on('selection:cleared', () => this.handleSelectionChanged([]));
461
+ const onObjectEvent = (e) => {
462
+ if (e.target && isMaskObject(e.target))
463
+ this.syncMaskLabel(e.target);
464
+ };
465
+ const onObjectModified = (e) => {
466
+ if (!e.target || !isMaskObject(e.target))
467
+ return;
468
+ this.syncMaskLabel(e.target);
469
+ this.saveState();
470
+ };
471
+ this.canvas.on('object:moving', onObjectEvent);
472
+ this.canvas.on('object:scaling', onObjectEvent);
473
+ this.canvas.on('object:rotating', onObjectEvent);
474
+ this.canvas.on('object:modified', onObjectModified);
475
+ }
476
+ bindDomEvents() {
477
+ this.bindElementIfExists('uploadArea', 'click', () => {
478
+ var _a;
479
+ const inputId = this.elements.imageInput;
480
+ if (inputId)
481
+ (_a = document.getElementById(inputId)) === null || _a === void 0 ? void 0 : _a.click();
482
+ });
483
+ this.bindElementIfExists('imageInput', 'change', (e) => {
484
+ var _a;
485
+ const file = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
486
+ if (file)
487
+ void this.loadImageFile(file);
488
+ });
489
+ this.bindElementIfExists('zoomInButton', 'click', () => {
490
+ void this.scaleImage(this.currentScale + this.options.scaleStep);
491
+ });
492
+ this.bindElementIfExists('zoomOutButton', 'click', () => {
493
+ void this.scaleImage(this.currentScale - this.options.scaleStep);
494
+ });
495
+ this.bindElementIfExists('resetImageTransformButton', 'click', () => {
496
+ void this.resetImageTransform();
497
+ });
498
+ this.bindElementIfExists('createMaskButton', 'click', () => {
499
+ this.createMask();
500
+ });
501
+ this.bindElementIfExists('removeSelectedMaskButton', 'click', () => {
502
+ this.removeSelectedMask();
503
+ });
504
+ this.bindElementIfExists('removeAllMasksButton', 'click', () => {
505
+ this.removeAllMasks();
506
+ });
507
+ this.bindElementIfExists('mergeMasksButton', 'click', () => {
508
+ void this.mergeMasks();
509
+ });
510
+ this.bindElementIfExists('downloadImageButton', 'click', () => {
511
+ this.downloadImage();
512
+ });
513
+ this.bindElementIfExists('undoButton', 'click', () => {
514
+ this.undo();
515
+ });
516
+ this.bindElementIfExists('redoButton', 'click', () => {
517
+ this.redo();
518
+ });
519
+ this.bindElementIfExists('rotateLeftButton', 'click', () => {
520
+ const inputId = this.elements.rotateLeftDegreesInput;
521
+ const inputEl = inputId
522
+ ? document.getElementById(inputId)
523
+ : null;
524
+ let step = this.options.rotationStep;
525
+ if (inputEl) {
526
+ const parsedStep = parseFloat(inputEl.value);
527
+ if (!isNaN(parsedStep))
528
+ step = parsedStep;
529
+ }
530
+ void this.rotateImage(this.currentRotation - step);
531
+ });
532
+ this.bindElementIfExists('rotateRightButton', 'click', () => {
533
+ const inputId = this.elements.rotateRightDegreesInput;
534
+ const inputEl = inputId
535
+ ? document.getElementById(inputId)
536
+ : null;
537
+ let step = this.options.rotationStep;
538
+ if (inputEl) {
539
+ const parsedStep = parseFloat(inputEl.value);
540
+ if (!isNaN(parsedStep))
541
+ step = parsedStep;
542
+ }
543
+ void this.rotateImage(this.currentRotation + step);
544
+ });
545
+ this.bindElementIfExists('enterCropModeButton', 'click', () => {
546
+ this.enterCropMode();
547
+ });
548
+ this.bindElementIfExists('applyCropButton', 'click', () => {
549
+ void this.applyCrop().catch((error) => {
550
+ reportError(this.options, error, 'Crop apply failed.');
551
+ });
552
+ });
553
+ this.bindElementIfExists('cancelCropButton', 'click', () => {
554
+ this.cancelCrop();
555
+ });
556
+ this.bindElementIfExists('enterMosaicModeButton', 'click', () => {
557
+ this.enterMosaicMode();
558
+ });
559
+ this.bindElementIfExists('exitMosaicModeButton', 'click', () => {
560
+ this.exitMosaicMode();
561
+ });
562
+ const bindMosaicSizeInput = (key, applyValue) => {
563
+ const handler = (event) => {
564
+ const parsed = parseFloat(event.target.value);
565
+ applyValue(parsed);
566
+ };
567
+ this.bindElementIfExists(key, 'input', handler);
568
+ this.bindElementIfExists(key, 'change', handler);
569
+ };
570
+ bindMosaicSizeInput('mosaicBrushSizeInput', (value) => {
571
+ this.setMosaicBrushSize(value);
572
+ });
573
+ bindMosaicSizeInput('mosaicBlockSizeInput', (value) => {
574
+ this.setMosaicBlockSize(value);
575
+ });
576
+ }
577
+ bindElementIfExists(key, event, handler) {
578
+ var _a;
579
+ (_a = this.domBindings) === null || _a === void 0 ? void 0 : _a.bindIfExists(key, event, handler);
580
+ }
581
+ async loadImageFile(file) {
582
+ const inputId = this.elements.imageInput;
583
+ const inputEl = inputId
584
+ ? document.getElementById(inputId)
585
+ : null;
586
+ const mime = inferImageMimeType(file);
587
+ if (!mime) {
588
+ reportWarning(this.options, null, `Unsupported image file type: ${file.type || file.name || 'unknown'}.`);
589
+ resetFileInput(inputEl);
590
+ return;
591
+ }
592
+ let dataUrl;
593
+ try {
594
+ dataUrl = await readFileAsDataUrl(file);
595
+ }
596
+ catch (error) {
597
+ reportError(this.options, error, 'Failed to read selected image file.');
598
+ resetFileInput(inputEl);
599
+ return;
600
+ }
601
+ try {
602
+ await this.loadImage(dataUrl);
603
+ }
604
+ catch {
605
+ }
606
+ finally {
607
+ resetFileInput(inputEl);
608
+ }
609
+ }
610
+ async loadImage(base64, options = {}) {
611
+ return this.loadImageInternal(base64, options);
612
+ }
613
+ async loadImageInternal(base64, options = {}) {
614
+ if (!this.isFabricLoaded || !this.canvas)
615
+ return;
616
+ if (this.isDisposed)
617
+ return;
618
+ if (typeof base64 !== 'string' || !base64.startsWith('data:image/'))
619
+ return;
620
+ if (!this.canRunIdleOperation('loadImage', options))
621
+ return;
622
+ const callbackContext = this.getOperationContext('loadImage', options);
623
+ const previousImage = this.originalImage;
624
+ const hadMasks = this.getMasks().length > 0;
625
+ this.emitOptionCallback('onImageLoadStart', [callbackContext]);
626
+ this.operationGuard.beginLoading();
627
+ this.emitBusyChangeIfChanged(callbackContext);
628
+ this.updateUi();
629
+ this.hideAllMaskLabels();
630
+ const loadImageContext = {
631
+ fabric: this.fabricModule,
632
+ canvas: this.canvas,
633
+ options: this.getRuntimeOptions(),
634
+ containerElement: this.containerElement,
635
+ placeholderElement: this.placeholderElement,
636
+ viewportCache: this.viewportCache,
637
+ getOriginalImage: () => this.originalImage,
638
+ setOriginalImage: (v) => {
639
+ this.originalImage = v;
640
+ },
641
+ getIsImageLoadedToCanvas: () => this.isImageLoadedToCanvas,
642
+ setIsImageLoadedToCanvas: (v) => {
643
+ this.isImageLoadedToCanvas = v;
644
+ },
645
+ getLastSnapshot: () => this.lastSnapshot,
646
+ setLastSnapshot: (v) => {
647
+ this.lastSnapshot = v;
648
+ },
649
+ getMaskCounter: () => this.maskCounter,
650
+ setMaskCounter: (v) => {
651
+ this.maskCounter = v;
652
+ },
653
+ getCurrentScale: () => this.currentScale,
654
+ setCurrentScale: (v) => {
655
+ this.currentScale = v;
656
+ },
657
+ getCurrentRotation: () => this.currentRotation,
658
+ setCurrentRotation: (v) => {
659
+ this.currentRotation = v;
660
+ },
661
+ getBaseImageScale: () => this.baseImageScale,
662
+ setBaseImageScale: (v) => {
663
+ this.baseImageScale = v;
664
+ },
665
+ getCurrentImageMimeType: () => this.currentImageMimeType,
666
+ setCurrentImageMimeType: (v) => {
667
+ this.currentImageMimeType = v;
668
+ },
669
+ setPlaceholderVisible: (show) => {
670
+ setPlaceholderVisibleImpl(this.placeholderElement, this.containerElement, this.options.showPlaceholder ? show : false);
671
+ },
672
+ };
673
+ try {
674
+ await loadImageImpl(loadImageContext, base64, options);
675
+ }
676
+ finally {
677
+ this.operationGuard.endLoading();
678
+ this.emitBusyChangeIfChanged(callbackContext);
679
+ if (!this.isDisposed && this.canvas)
680
+ this.updateUi();
681
+ }
682
+ this.lastMask = null;
683
+ this.updateInputs();
684
+ this.updateMaskList();
685
+ this.updateUi();
686
+ if (previousImage && previousImage !== this.originalImage) {
687
+ this.emitOptionCallback('onImageCleared', [previousImage, callbackContext]);
688
+ }
689
+ const imageInfo = this.getImageInfo();
690
+ if (imageInfo) {
691
+ this.emitOptionCallback('onImageLoaded', [imageInfo, callbackContext]);
692
+ }
693
+ if (hadMasks) {
694
+ this.emitMasksChanged(callbackContext);
695
+ }
696
+ this.emitImageChanged(callbackContext);
697
+ }
698
+ getInternalOperationToken(options) {
699
+ var _a;
700
+ return ((_a = options === null || options === void 0 ? void 0 : options[INTERNAL_OPERATION_TOKEN]) !== null && _a !== void 0 ? _a : null);
701
+ }
702
+ canRunDuringAnimationQueue(options) {
703
+ return !!(options === null || options === void 0 ? void 0 : options[INTERNAL_ALLOW_DURING_ANIMATION_QUEUE]);
704
+ }
705
+ withInternalOperationOptions(token, options = {}) {
706
+ return {
707
+ ...options,
708
+ ...(token ? { [INTERNAL_OPERATION_TOKEN]: token } : {}),
709
+ };
710
+ }
711
+ withAnimationQueueBypass(options = {}) {
712
+ return {
713
+ ...options,
714
+ [INTERNAL_ALLOW_DURING_ANIMATION_QUEUE]: true,
715
+ };
716
+ }
717
+ assertIdleForOperation(operationName, options) {
718
+ const token = this.getInternalOperationToken(options);
719
+ this.operationGuard.assertIdleForOperation(operationName, token);
720
+ if (this.cropSession &&
721
+ !this.operationGuard.isOwnOperation(token) &&
722
+ !CROP_SESSION_ALLOWED_OPERATIONS.has(operationName)) {
723
+ throw new Error(`[ImageEditor] Cannot run "${operationName}" while crop mode is active.`);
724
+ }
725
+ if (this.mosaicSession &&
726
+ !this.operationGuard.isOwnOperation(token) &&
727
+ !MOSAIC_SESSION_ALLOWED_OPERATIONS.has(operationName)) {
728
+ throw new Error(`[ImageEditor] Cannot run "${operationName}" while mosaic mode is active.`);
729
+ }
730
+ if (this.animQueue.isBusy() && !this.canRunDuringAnimationQueue(options)) {
731
+ throw new Error(`[ImageEditor] Cannot run "${operationName}" while an animation is queued.`);
732
+ }
733
+ }
734
+ canRunIdleOperation(operationName, options) {
735
+ try {
736
+ this.assertIdleForOperation(operationName, options);
737
+ return true;
738
+ }
739
+ catch (error) {
740
+ if (!this.isExpectedIdleGuardError(error, operationName)) {
741
+ throw error;
742
+ }
743
+ return false;
744
+ }
745
+ }
746
+ isExpectedIdleGuardError(error, operationName) {
747
+ return (error instanceof Error &&
748
+ error.message.startsWith(`[ImageEditor] Cannot run "${operationName}" `));
749
+ }
750
+ assertCanQueueAnimation(operationName, options) {
751
+ this.operationGuard.assertCanQueueAnimation(operationName, this.getInternalOperationToken(options));
752
+ }
753
+ isImageLoaded() {
754
+ var _a, _b;
755
+ return !!(this.originalImage &&
756
+ this.originalImage instanceof this.fabricModule.FabricImage &&
757
+ ((_a = this.originalImage.width) !== null && _a !== void 0 ? _a : 0) > 0 &&
758
+ ((_b = this.originalImage.height) !== null && _b !== void 0 ? _b : 0) > 0);
759
+ }
760
+ isBusy() {
761
+ return (this.operationGuard.isBusy() ||
762
+ this.animQueue.isBusy() ||
763
+ this.cropSession !== null ||
764
+ this.mosaicSession !== null);
765
+ }
766
+ setLayoutMode(mode) {
767
+ if (!isLayoutMode(mode)) {
768
+ reportWarning(this.options, new TypeError(`[ImageEditor] Unsupported layout mode ${JSON.stringify(mode)}. ` +
769
+ 'Expected "fit", "cover", or "expand".'), 'Ignored invalid layout mode.');
770
+ return;
771
+ }
772
+ this.currentLayoutMode = mode;
773
+ }
774
+ getRuntimeOptions() {
775
+ if (this.currentLayoutMode === this.options.layoutMode)
776
+ return this.options;
777
+ return Object.freeze({
778
+ ...this.options,
779
+ layoutMode: this.currentLayoutMode,
780
+ });
781
+ }
782
+ buildCallbackContext(operation, isInternalOperation = false) {
783
+ return { operation, isInternalOperation };
784
+ }
785
+ getOperationContext(fallback, options) {
786
+ const internal = this.getInternalOperationToken(options);
787
+ const activeOperation = this.operationGuard.activeOperationName();
788
+ if (internal && activeOperation) {
789
+ return this.buildCallbackContext(isImageEditorOperation(activeOperation) ? activeOperation : fallback, true);
790
+ }
791
+ return this.buildCallbackContext(fallback, false);
792
+ }
793
+ emitOptionCallback(callbackName, args) {
794
+ const callback = this.options[callbackName];
795
+ if (typeof callback !== 'function')
796
+ return;
797
+ try {
798
+ callback(...args);
799
+ }
800
+ catch (error) {
801
+ console.error(`[ImageEditor] ${callbackName} callback threw`, error);
802
+ }
803
+ }
804
+ getImageInfo() {
805
+ if (!this.canvas || !this.originalImage)
806
+ return null;
807
+ const canvasWidth = this.canvas.getWidth();
808
+ const canvasHeight = this.canvas.getHeight();
809
+ let displayWidth;
810
+ let displayHeight;
811
+ try {
812
+ this.originalImage.setCoords();
813
+ const bounds = this.originalImage.getBoundingRect();
814
+ displayWidth = Math.max(0, Number(bounds.width) || 0);
815
+ displayHeight = Math.max(0, Number(bounds.height) || 0);
816
+ }
817
+ catch {
818
+ displayWidth = Math.max(0, (Number(this.originalImage.width) || 0) *
819
+ Math.abs(Number(this.originalImage.scaleX) || 1));
820
+ displayHeight = Math.max(0, (Number(this.originalImage.height) || 0) *
821
+ Math.abs(Number(this.originalImage.scaleY) || 1));
822
+ }
823
+ return {
824
+ width: Math.max(0, Number(this.originalImage.width) || 0),
825
+ height: Math.max(0, Number(this.originalImage.height) || 0),
826
+ displayWidth,
827
+ displayHeight,
828
+ scale: this.currentScale,
829
+ rotation: this.currentRotation,
830
+ canvasWidth,
831
+ canvasHeight,
832
+ };
833
+ }
834
+ getMasks() {
835
+ if (!this.canvas)
836
+ return [];
837
+ return this.canvas.getObjects().filter(isMaskObject).slice();
838
+ }
839
+ getMaskCollectionSignature() {
840
+ return this.getMasks()
841
+ .map((mask) => `${mask.maskId}:${mask.maskName}`)
842
+ .join('|');
843
+ }
844
+ getEditorState() {
845
+ const canvasWidth = this.canvas ? this.canvas.getWidth() : 0;
846
+ const canvasHeight = this.canvas ? this.canvas.getHeight() : 0;
847
+ const image = this.getImageInfo();
848
+ return {
849
+ hasImage: image !== null,
850
+ image,
851
+ maskCount: this.getMasks().length,
852
+ currentScale: this.currentScale,
853
+ currentRotation: this.currentRotation,
854
+ isBusy: this.isBusy(),
855
+ isCropMode: this.cropSession !== null,
856
+ isMosaicMode: this.mosaicSession !== null,
857
+ canUndo: this.historyManager.canUndo(),
858
+ canRedo: this.historyManager.canRedo(),
859
+ canvasWidth,
860
+ canvasHeight,
861
+ };
862
+ }
863
+ emitImageChanged(context) {
864
+ this.emitOptionCallback('onImageChanged', [this.getEditorState(), context]);
865
+ }
866
+ emitMasksChanged(context) {
867
+ this.emitOptionCallback('onMasksChanged', [this.getMasks(), context]);
868
+ }
869
+ emitBusyChangeIfChanged(context) {
870
+ const isBusy = this.isBusy();
871
+ if (this.lastEmittedIsBusy === isBusy)
872
+ return;
873
+ this.lastEmittedIsBusy = isBusy;
874
+ this.emitOptionCallback('onBusyChange', [isBusy, context]);
875
+ }
876
+ buildSelection(selected) {
877
+ var _a;
878
+ const selectedMasks = selected.filter(isMaskObject);
879
+ return {
880
+ selectedMask: (_a = selectedMasks[0]) !== null && _a !== void 0 ? _a : null,
881
+ selectedMasks,
882
+ };
883
+ }
884
+ withSelectionChangeContext(context, callback) {
885
+ const previous = this.nextSelectionChangeContext;
886
+ this.nextSelectionChangeContext = context;
887
+ try {
888
+ return callback();
889
+ }
890
+ finally {
891
+ this.nextSelectionChangeContext = previous;
892
+ }
893
+ }
894
+ isSupportedImageMimeType(mimeType) {
895
+ return mimeType === 'image/jpeg' || mimeType === 'image/png' || mimeType === 'image/webp';
896
+ }
897
+ inferCurrentImageMimeType() {
898
+ const image = this.originalImage;
899
+ if (!image)
900
+ return null;
901
+ let source = null;
902
+ try {
903
+ if (typeof image.getSrc === 'function')
904
+ source = image.getSrc();
905
+ else if (typeof image.src === 'string')
906
+ source = image.src;
907
+ }
908
+ catch {
909
+ source = null;
910
+ }
911
+ const mimeType = source ? detectSourceMimeType(source) : null;
912
+ return this.isSupportedImageMimeType(mimeType) ? mimeType : null;
913
+ }
914
+ setCanvasSizePx(widthPx, heightPx) {
915
+ if (!this.canvas)
916
+ return;
917
+ applyCanvasDimensions(this.canvas, widthPx, heightPx, this.containerElement);
918
+ }
919
+ alignObjectBoundingBoxToCanvasTopLeft(object) {
920
+ var _a, _b;
921
+ object.setCoords();
922
+ const boundingRect = object.getBoundingRect();
923
+ object.set({
924
+ left: ((_a = object.left) !== null && _a !== void 0 ? _a : 0) - boundingRect.left,
925
+ top: ((_b = object.top) !== null && _b !== void 0 ? _b : 0) - boundingRect.top,
926
+ });
927
+ object.setCoords();
928
+ this.canvas.renderAll();
929
+ }
930
+ measureLayoutViewport(scrollbarSize) {
931
+ return this.viewportCache.measure(this.containerElement, {
932
+ width: this.options.canvasWidth,
933
+ height: this.options.canvasHeight,
934
+ }, scrollbarSize);
935
+ }
936
+ updateCanvasSizeToImageBounds() {
937
+ var _a, _b;
938
+ if (!this.originalImage)
939
+ return;
940
+ this.originalImage.setCoords();
941
+ const boundingRect = this.originalImage.getBoundingRect();
942
+ const scrollbarSize = measureScrollbarSize((_b = (_a = this.containerElement) === null || _a === void 0 ? void 0 : _a.ownerDocument) !== null && _b !== void 0 ? _b : null);
943
+ const viewport = this.measureLayoutViewport(scrollbarSize);
944
+ if (this.currentLayoutMode === 'fit' || this.currentLayoutMode === 'cover') {
945
+ const canvasSize = computeScrollableCanvasSize(boundingRect.width, boundingRect.height, viewport, scrollbarSize);
946
+ this.setCanvasSizePx(canvasSize.width, canvasSize.height);
947
+ return;
948
+ }
949
+ if (boundingRect.width <= viewport.width && boundingRect.height <= viewport.height) {
950
+ this.setCanvasSizePx(viewport.width, viewport.height);
951
+ return;
952
+ }
953
+ this.setCanvasSizePx(Math.max(viewport.width, Math.ceil(boundingRect.width)), Math.max(viewport.height, Math.ceil(boundingRect.height)));
954
+ }
955
+ shouldNormalizeCanvasSizeAfterStateRestore() {
956
+ var _a, _b;
957
+ if (!this.canvas || !this.originalImage)
958
+ return false;
959
+ this.originalImage.setCoords();
960
+ const boundingRect = this.originalImage.getBoundingRect();
961
+ const viewport = this.measureLayoutViewport(measureScrollbarSize((_b = (_a = this.containerElement) === null || _a === void 0 ? void 0 : _a.ownerDocument) !== null && _b !== void 0 ? _b : null));
962
+ const canvasW = Math.ceil(this.canvas.getWidth());
963
+ const canvasH = Math.ceil(this.canvas.getHeight());
964
+ const clipsImage = boundingRect.width > canvasW + LAYOUT_EPSILON ||
965
+ boundingRect.height > canvasH + LAYOUT_EPSILON;
966
+ if (this.currentLayoutMode === 'fit' || this.currentLayoutMode === 'cover') {
967
+ const staleOverflowWidth = canvasW > viewport.width + LAYOUT_EPSILON &&
968
+ boundingRect.width <= viewport.width + LAYOUT_EPSILON;
969
+ const staleOverflowHeight = canvasH > viewport.height + LAYOUT_EPSILON &&
970
+ boundingRect.height <= viewport.height + LAYOUT_EPSILON;
971
+ return clipsImage || staleOverflowWidth || staleOverflowHeight;
972
+ }
973
+ if (this.currentLayoutMode === 'expand') {
974
+ const expectedW = Math.max(viewport.width, Math.ceil(boundingRect.width));
975
+ const expectedH = Math.max(viewport.height, Math.ceil(boundingRect.height));
976
+ return (Math.abs(canvasW - expectedW) > LAYOUT_EPSILON ||
977
+ Math.abs(canvasH - expectedH) > LAYOUT_EPSILON);
978
+ }
979
+ return clipsImage;
980
+ }
981
+ settleFitCoverScrollbarsAfterStateRestore() {
982
+ if (!this.canvas ||
983
+ !this.containerElement ||
984
+ (this.currentLayoutMode !== 'fit' && this.currentLayoutMode !== 'cover')) {
985
+ return;
986
+ }
987
+ const canvasW = Math.ceil(this.canvas.getWidth());
988
+ const canvasH = Math.ceil(this.canvas.getHeight());
989
+ if (canvasW <= 1 || canvasH <= 1)
990
+ return;
991
+ const clientW = Math.floor(this.containerElement.clientWidth || 0);
992
+ const clientH = Math.floor(this.containerElement.clientHeight || 0);
993
+ if (clientW <= 0 || clientH <= 0)
994
+ return;
995
+ const scrollW = Math.ceil(this.containerElement.scrollWidth || 0);
996
+ const scrollH = Math.ceil(this.containerElement.scrollHeight || 0);
997
+ const hasHorizontalScrollbar = scrollW > clientW + LAYOUT_EPSILON;
998
+ const hasVerticalScrollbar = scrollH > clientH + LAYOUT_EPSILON;
999
+ if (!hasHorizontalScrollbar && !hasVerticalScrollbar)
1000
+ return;
1001
+ const nudgeWidth = hasVerticalScrollbar && Math.abs(canvasW - clientW) <= SCROLLBAR_SETTLE_EPSILON;
1002
+ const nudgeHeight = hasHorizontalScrollbar && Math.abs(canvasH - clientH) <= SCROLLBAR_SETTLE_EPSILON;
1003
+ if (!nudgeWidth && !nudgeHeight)
1004
+ return;
1005
+ this.setCanvasSizePx(nudgeWidth ? canvasW - 1 : canvasW, nudgeHeight ? canvasH - 1 : canvasH);
1006
+ this.setCanvasSizePx(canvasW, canvasH);
1007
+ }
1008
+ captureImageDisplayGeometry() {
1009
+ if (!this.canvas || !this.originalImage)
1010
+ return null;
1011
+ this.originalImage.setCoords();
1012
+ const boundingRect = this.originalImage.getBoundingRect();
1013
+ return {
1014
+ canvasWidth: this.canvas.getWidth(),
1015
+ canvasHeight: this.canvas.getHeight(),
1016
+ imageDisplayWidth: Math.max(1, boundingRect.width),
1017
+ imageDisplayHeight: Math.max(1, boundingRect.height),
1018
+ };
1019
+ }
1020
+ restoreMergedImageDisplayGeometry(geometry) {
1021
+ if (!geometry || !this.canvas || !this.originalImage)
1022
+ return;
1023
+ this.setCanvasSizePx(geometry.canvasWidth, geometry.canvasHeight);
1024
+ const sourceW = Math.max(1, this.originalImage.width || geometry.imageDisplayWidth);
1025
+ const sourceH = Math.max(1, this.originalImage.height || geometry.imageDisplayHeight);
1026
+ const scale = Math.min(geometry.imageDisplayWidth / sourceW, geometry.imageDisplayHeight / sourceH);
1027
+ this.originalImage.set({
1028
+ left: 0,
1029
+ top: 0,
1030
+ angle: 0,
1031
+ scaleX: scale,
1032
+ scaleY: scale,
1033
+ originX: 'left',
1034
+ originY: 'top',
1035
+ selectable: false,
1036
+ evented: false,
1037
+ hasControls: false,
1038
+ hoverCursor: 'default',
1039
+ });
1040
+ this.originalImage.setCoords();
1041
+ this.canvas.sendObjectToBack(this.originalImage);
1042
+ this.currentScale = 1;
1043
+ this.currentRotation = 0;
1044
+ this.baseImageScale = scale;
1045
+ this.lastSnapshot = this.captureSnapshotInternal();
1046
+ this.canvas.renderAll();
1047
+ }
1048
+ buildTransformContext() {
1049
+ return {
1050
+ canvas: this.canvas,
1051
+ options: this.options,
1052
+ guard: this.operationGuard,
1053
+ getOriginalImage: () => this.originalImage,
1054
+ getCurrentScale: () => this.currentScale,
1055
+ setCurrentScale: (n) => {
1056
+ this.currentScale = n;
1057
+ },
1058
+ getCurrentRotation: () => this.currentRotation,
1059
+ setCurrentRotation: (n) => {
1060
+ this.currentRotation = n;
1061
+ },
1062
+ getBaseImageScale: () => this.baseImageScale,
1063
+ saveCanvasState: () => {
1064
+ this.saveStateInternal(this.withAnimationQueueBypass());
1065
+ },
1066
+ setSuppressSaveState: (suppress) => {
1067
+ this.shouldSuppressSaveState = suppress;
1068
+ },
1069
+ afterTransformSnap: () => {
1070
+ if (this.isDisposed || !this.canvas || !this.originalImage)
1071
+ return;
1072
+ this.updateCanvasSizeToImageBounds();
1073
+ this.alignObjectBoundingBoxToCanvasTopLeft(this.originalImage);
1074
+ this.canvas
1075
+ .getObjects()
1076
+ .filter(isMaskObject)
1077
+ .forEach((maskObject) => this.syncMaskLabel(maskObject));
1078
+ },
1079
+ };
1080
+ }
1081
+ scaleImage(factor) {
1082
+ if (this.isDisposed || !this.transformController)
1083
+ return Promise.resolve();
1084
+ if (!Number.isFinite(factor))
1085
+ return Promise.resolve();
1086
+ try {
1087
+ this.assertCanQueueAnimation('scaleImage');
1088
+ }
1089
+ catch (error) {
1090
+ return Promise.reject(error);
1091
+ }
1092
+ const controller = this.transformController;
1093
+ const context = this.buildCallbackContext('scaleImage', false);
1094
+ const job = this.animQueue.add(async () => {
1095
+ if (this.isDisposed)
1096
+ return;
1097
+ this.updateUi();
1098
+ try {
1099
+ await controller.scaleImage(factor);
1100
+ if (!this.isDisposed)
1101
+ this.emitImageChanged(context);
1102
+ }
1103
+ finally {
1104
+ if (!this.isDisposed) {
1105
+ this.updateInputs();
1106
+ }
1107
+ }
1108
+ });
1109
+ this.emitBusyChangeIfChanged(context);
1110
+ return job.finally(() => {
1111
+ this.refreshUiAfterQueuedAnimation();
1112
+ this.emitBusyChangeIfChanged(context);
1113
+ });
1114
+ }
1115
+ rotateImage(degrees) {
1116
+ if (this.isDisposed || !this.transformController)
1117
+ return Promise.resolve();
1118
+ if (!Number.isFinite(degrees))
1119
+ return Promise.resolve();
1120
+ try {
1121
+ this.assertCanQueueAnimation('rotateImage');
1122
+ }
1123
+ catch (error) {
1124
+ return Promise.reject(error);
1125
+ }
1126
+ const controller = this.transformController;
1127
+ const context = this.buildCallbackContext('rotateImage', false);
1128
+ const job = this.animQueue.add(async () => {
1129
+ if (this.isDisposed)
1130
+ return;
1131
+ this.updateUi();
1132
+ try {
1133
+ await controller.rotateImage(degrees);
1134
+ if (!this.isDisposed)
1135
+ this.emitImageChanged(context);
1136
+ }
1137
+ finally {
1138
+ if (!this.isDisposed) {
1139
+ this.updateInputs();
1140
+ }
1141
+ }
1142
+ });
1143
+ this.emitBusyChangeIfChanged(context);
1144
+ return job.finally(() => {
1145
+ this.refreshUiAfterQueuedAnimation();
1146
+ this.emitBusyChangeIfChanged(context);
1147
+ });
1148
+ }
1149
+ resetImageTransform() {
1150
+ if (this.isDisposed || !this.transformController)
1151
+ return Promise.resolve();
1152
+ try {
1153
+ this.assertCanQueueAnimation('resetImageTransform');
1154
+ }
1155
+ catch (error) {
1156
+ return Promise.reject(error);
1157
+ }
1158
+ const controller = this.transformController;
1159
+ const context = this.buildCallbackContext('resetImageTransform', false);
1160
+ const job = this.animQueue.add(async () => {
1161
+ if (this.isDisposed)
1162
+ return;
1163
+ this.updateUi();
1164
+ try {
1165
+ await controller.resetImageTransform();
1166
+ if (!this.isDisposed)
1167
+ this.emitImageChanged(context);
1168
+ }
1169
+ finally {
1170
+ if (!this.isDisposed) {
1171
+ this.updateInputs();
1172
+ }
1173
+ }
1174
+ });
1175
+ this.emitBusyChangeIfChanged(context);
1176
+ return job.finally(() => {
1177
+ this.refreshUiAfterQueuedAnimation();
1178
+ this.emitBusyChangeIfChanged(context);
1179
+ });
1180
+ }
1181
+ refreshUiAfterQueuedAnimation() {
1182
+ if (this.isDisposed || !this.canvas)
1183
+ return;
1184
+ this.updateInputs();
1185
+ this.updateUi();
1186
+ }
1187
+ async loadFromState(jsonString) {
1188
+ return this.loadFromStateInternal(jsonString);
1189
+ }
1190
+ async loadFromStateInternal(jsonString, options) {
1191
+ var _a;
1192
+ if (!jsonString || !this.canvas)
1193
+ return;
1194
+ if (this.isDisposed)
1195
+ return;
1196
+ if (!this.canRunIdleOperation('loadFromState', options))
1197
+ return;
1198
+ const activeRestoreOperation = this.activeStateRestoreOperation;
1199
+ const context = this.buildCallbackContext(activeRestoreOperation !== null && activeRestoreOperation !== void 0 ? activeRestoreOperation : 'loadFromState', activeRestoreOperation === 'undo' || activeRestoreOperation === 'redo');
1200
+ const previousImage = this.originalImage;
1201
+ const previousMaskSignature = this.getMaskCollectionSignature();
1202
+ try {
1203
+ const restoredState = await loadFromStateImpl({
1204
+ canvas: this.canvas,
1205
+ jsonString,
1206
+ setCanvasSize: (widthPx, heightPx) => this.setCanvasSizePx(widthPx, heightPx),
1207
+ });
1208
+ if (this.isDisposed || !this.canvas)
1209
+ return;
1210
+ this.hideAllMaskLabels();
1211
+ this.originalImage = restoredState.originalImage;
1212
+ if (this.originalImage) {
1213
+ this.originalImage.set({
1214
+ originX: 'left',
1215
+ originY: 'top',
1216
+ selectable: false,
1217
+ evented: false,
1218
+ hasControls: false,
1219
+ hoverCursor: 'default',
1220
+ });
1221
+ this.canvas.sendObjectToBack(this.originalImage);
1222
+ }
1223
+ this.maskCounter = restoredState.maxMaskId;
1224
+ const editorState = restoredState.editorState;
1225
+ if (editorState) {
1226
+ this.currentScale = editorState.currentScale;
1227
+ this.currentRotation = editorState.currentRotation;
1228
+ this.baseImageScale = editorState.baseImageScale;
1229
+ }
1230
+ if (this.originalImage) {
1231
+ this.currentImageMimeType =
1232
+ editorState && 'currentImageMimeType' in editorState
1233
+ ? ((_a = editorState.currentImageMimeType) !== null && _a !== void 0 ? _a : null)
1234
+ : this.inferCurrentImageMimeType();
1235
+ }
1236
+ else {
1237
+ this.currentImageMimeType = null;
1238
+ }
1239
+ this.isImageLoadedToCanvas = !!this.originalImage;
1240
+ if (this.originalImage && this.shouldNormalizeCanvasSizeAfterStateRestore()) {
1241
+ this.updateCanvasSizeToImageBounds();
1242
+ this.alignObjectBoundingBoxToCanvasTopLeft(this.originalImage);
1243
+ }
1244
+ if (this.originalImage) {
1245
+ this.settleFitCoverScrollbarsAfterStateRestore();
1246
+ }
1247
+ const restoredMasks = restoredState.objects.filter(isMaskObject);
1248
+ this.lastMask = restoredMasks.reduce((lastMask, maskObject) => !lastMask || maskObject.maskId > lastMask.maskId ? maskObject : lastMask, null);
1249
+ restoredMasks.forEach((maskObject) => {
1250
+ applyMaskUnselectedStyle(maskObject);
1251
+ reattachMaskHoverHandlers(maskObject);
1252
+ });
1253
+ this.lastSnapshot = this.captureSnapshotInternal();
1254
+ this.canvas.renderAll();
1255
+ this.updateInputs();
1256
+ this.updateMaskList();
1257
+ this.updateUi();
1258
+ if (previousImage && previousImage !== this.originalImage) {
1259
+ this.emitOptionCallback('onImageCleared', [previousImage, context]);
1260
+ }
1261
+ if (previousMaskSignature !== this.getMaskCollectionSignature()) {
1262
+ this.emitMasksChanged(context);
1263
+ }
1264
+ this.emitImageChanged(context);
1265
+ const activeMaskId = editorState === null || editorState === void 0 ? void 0 : editorState.activeMaskId;
1266
+ if (typeof activeMaskId === 'number') {
1267
+ const activeMask = restoredMasks.find((maskObject) => maskObject.maskId === activeMaskId);
1268
+ if (activeMask) {
1269
+ this.withSelectionChangeContext(context, () => {
1270
+ this.canvas.setActiveObject(activeMask);
1271
+ this.handleSelectionChanged([activeMask]);
1272
+ });
1273
+ }
1274
+ }
1275
+ }
1276
+ catch (error) {
1277
+ reportError(this.options, error, 'Failed to restore canvas state.');
1278
+ throw error;
1279
+ }
1280
+ }
1281
+ saveState() {
1282
+ this.saveStateInternal();
1283
+ }
1284
+ saveStateInternal(options) {
1285
+ var _a, _b;
1286
+ if (!this.canvas || this.shouldSuppressSaveState)
1287
+ return;
1288
+ if (!this.canRunIdleOperation('saveState', options))
1289
+ return;
1290
+ const activeObj = this.canvas.getActiveObject();
1291
+ const activeMask = this.getActiveMaskForSnapshot();
1292
+ this.hideAllMaskLabels();
1293
+ try {
1294
+ const after = saveStateImpl({
1295
+ canvas: this.canvas,
1296
+ activeMaskId: (_a = activeMask === null || activeMask === void 0 ? void 0 : activeMask.maskId) !== null && _a !== void 0 ? _a : null,
1297
+ currentScale: this.currentScale,
1298
+ currentRotation: this.currentRotation,
1299
+ baseImageScale: this.baseImageScale,
1300
+ currentImageMimeType: this.currentImageMimeType,
1301
+ });
1302
+ const before = (_b = this.lastSnapshot) !== null && _b !== void 0 ? _b : after;
1303
+ if (after === before) {
1304
+ return;
1305
+ }
1306
+ const cmd = new Command(async () => {
1307
+ await this.loadFromStateInternal(after, this.withAnimationQueueBypass());
1308
+ }, async () => {
1309
+ await this.loadFromStateInternal(before, this.withAnimationQueueBypass());
1310
+ });
1311
+ this.historyManager.push(cmd);
1312
+ this.lastSnapshot = after;
1313
+ }
1314
+ catch (error) {
1315
+ reportWarning(this.options, error, 'Failed to capture canvas snapshot.');
1316
+ }
1317
+ finally {
1318
+ this.restoreActiveMaskAfterSnapshot(activeObj, activeMask);
1319
+ this.updateUi();
1320
+ }
1321
+ }
1322
+ restoreActiveMaskAfterSnapshot(activeObj, activeMask) {
1323
+ if (!this.canvas)
1324
+ return;
1325
+ const maskToRestore = activeObj && isMaskObject(activeObj) ? activeObj : activeMask;
1326
+ if (!maskToRestore || !this.canvas.getObjects().includes(maskToRestore))
1327
+ return;
1328
+ this.canvas.setActiveObject(maskToRestore);
1329
+ this.showLabelForMask(maskToRestore);
1330
+ this.updateMaskListSelection(maskToRestore);
1331
+ }
1332
+ undo() {
1333
+ if (this.isDisposed)
1334
+ return Promise.resolve();
1335
+ if (!this.canRunIdleOperation('undo'))
1336
+ return Promise.resolve();
1337
+ const context = this.buildCallbackContext('undo', true);
1338
+ const job = this.animQueue.add(async () => {
1339
+ if (this.isDisposed)
1340
+ return;
1341
+ this.activeStateRestoreOperation = 'undo';
1342
+ try {
1343
+ await this.historyManager.undo();
1344
+ }
1345
+ finally {
1346
+ this.activeStateRestoreOperation = null;
1347
+ }
1348
+ });
1349
+ this.emitBusyChangeIfChanged(context);
1350
+ return job.finally(() => {
1351
+ this.refreshUiAfterQueuedAnimation();
1352
+ this.emitBusyChangeIfChanged(context);
1353
+ });
1354
+ }
1355
+ redo() {
1356
+ if (this.isDisposed)
1357
+ return Promise.resolve();
1358
+ if (!this.canRunIdleOperation('redo'))
1359
+ return Promise.resolve();
1360
+ const context = this.buildCallbackContext('redo', true);
1361
+ const job = this.animQueue.add(async () => {
1362
+ if (this.isDisposed)
1363
+ return;
1364
+ this.activeStateRestoreOperation = 'redo';
1365
+ try {
1366
+ await this.historyManager.redo();
1367
+ }
1368
+ finally {
1369
+ this.activeStateRestoreOperation = null;
1370
+ }
1371
+ });
1372
+ this.emitBusyChangeIfChanged(context);
1373
+ return job.finally(() => {
1374
+ this.refreshUiAfterQueuedAnimation();
1375
+ this.emitBusyChangeIfChanged(context);
1376
+ });
1377
+ }
1378
+ createMask(config = {}) {
1379
+ if (!this.canvas)
1380
+ return null;
1381
+ if (!this.canRunIdleOperation('createMask'))
1382
+ return null;
1383
+ const callbackContext = this.buildCallbackContext('createMask', false);
1384
+ const createMaskContext = this.buildCreateMaskContext();
1385
+ const mask = this.withSelectionChangeContext(callbackContext, () => createMaskImpl(createMaskContext, config));
1386
+ if (mask) {
1387
+ this.emitMasksChanged(callbackContext);
1388
+ this.emitImageChanged(callbackContext);
1389
+ }
1390
+ return mask;
1391
+ }
1392
+ removeSelectedMask() {
1393
+ if (!this.canvas)
1394
+ return;
1395
+ if (!this.canRunIdleOperation('removeSelectedMask'))
1396
+ return;
1397
+ const before = this.getMasks().length;
1398
+ const callbackContext = this.buildCallbackContext('removeSelectedMask', false);
1399
+ const removeMaskContext = this.buildRemoveMaskContext();
1400
+ this.withSelectionChangeContext(callbackContext, () => removeSelectedMaskImpl(removeMaskContext));
1401
+ this.updateUi();
1402
+ if (this.getMasks().length !== before) {
1403
+ this.emitMasksChanged(callbackContext);
1404
+ this.emitImageChanged(callbackContext);
1405
+ }
1406
+ }
1407
+ removeAllMasks(options = {}) {
1408
+ if (!this.canvas)
1409
+ return;
1410
+ if (!this.canRunIdleOperation('removeAllMasks', options))
1411
+ return;
1412
+ const before = this.getMasks().length;
1413
+ const callbackContext = this.buildCallbackContext('removeAllMasks', false);
1414
+ const removeMaskContext = this.buildRemoveMaskContext();
1415
+ this.withSelectionChangeContext(callbackContext, () => removeAllMasksImpl(removeMaskContext, options));
1416
+ this.updateUi();
1417
+ if (this.getMasks().length !== before) {
1418
+ this.emitMasksChanged(callbackContext);
1419
+ this.emitImageChanged(callbackContext);
1420
+ }
1421
+ }
1422
+ buildCreateMaskContext() {
1423
+ return {
1424
+ fabric: this.fabricModule,
1425
+ canvas: this.canvas,
1426
+ options: this.getRuntimeOptions(),
1427
+ getLastMask: () => this.lastMask,
1428
+ setLastMask: (maskObject) => {
1429
+ this.lastMask = maskObject;
1430
+ },
1431
+ getMaskCounter: () => this.maskCounter,
1432
+ setMaskCounter: (n) => {
1433
+ this.maskCounter = n;
1434
+ },
1435
+ updateMaskList: () => {
1436
+ this.updateMaskList();
1437
+ },
1438
+ saveCanvasState: () => {
1439
+ this.saveState();
1440
+ },
1441
+ expandCanvasIfNeeded: (widthPx, heightPx) => {
1442
+ this.setCanvasSizePx(widthPx, heightPx);
1443
+ },
1444
+ };
1445
+ }
1446
+ buildRemoveMaskContext() {
1447
+ return {
1448
+ canvas: this.canvas,
1449
+ removeLabelForMask: (mask) => {
1450
+ this.removeLabelForMask(mask);
1451
+ },
1452
+ updateMaskList: () => {
1453
+ this.updateMaskList();
1454
+ },
1455
+ saveCanvasState: () => {
1456
+ this.saveState();
1457
+ },
1458
+ setLastMask: (maskObject) => {
1459
+ this.lastMask = maskObject;
1460
+ },
1461
+ };
1462
+ }
1463
+ buildMaskLabelContext() {
1464
+ if (!this.canvas)
1465
+ return null;
1466
+ return { fabric: this.fabricModule, canvas: this.canvas, options: this.options };
1467
+ }
1468
+ removeLabelForMask(mask) {
1469
+ const context = this.buildMaskLabelContext();
1470
+ if (!context)
1471
+ return;
1472
+ removeLabelForMask(context, mask);
1473
+ }
1474
+ createLabelForMask(mask) {
1475
+ const context = this.buildMaskLabelContext();
1476
+ if (!context)
1477
+ return;
1478
+ createLabelForMask(context, mask);
1479
+ }
1480
+ hideAllMaskLabels() {
1481
+ const context = this.buildMaskLabelContext();
1482
+ if (!context)
1483
+ return;
1484
+ hideAllMaskLabels(context);
1485
+ }
1486
+ syncMaskLabel(mask) {
1487
+ const context = this.buildMaskLabelContext();
1488
+ if (!context)
1489
+ return;
1490
+ syncMaskLabel(context, mask);
1491
+ }
1492
+ showLabelForMask(mask) {
1493
+ const context = this.buildMaskLabelContext();
1494
+ if (!context)
1495
+ return;
1496
+ showLabelForMask(context, mask);
1497
+ }
1498
+ handleSelectionChanged(selected) {
1499
+ var _a, _b, _c;
1500
+ if (!this.canvas)
1501
+ return;
1502
+ const selectedMask = (_a = selected.find(isMaskObject)) !== null && _a !== void 0 ? _a : null;
1503
+ const masks = this.canvas.getObjects().filter(isMaskObject);
1504
+ masks.forEach((maskObject) => {
1505
+ if (maskObject !== selectedMask) {
1506
+ if (maskObject.labelObject) {
1507
+ this.removeLabelForMask(maskObject);
1508
+ }
1509
+ applyMaskUnselectedStyle(maskObject);
1510
+ }
1511
+ else {
1512
+ applyMaskSelectedStyle(maskObject);
1513
+ }
1514
+ });
1515
+ if (selectedMask)
1516
+ this.showLabelForMask(selectedMask);
1517
+ this.updateMaskListSelection(selectedMask);
1518
+ this.canvas.requestRenderAll();
1519
+ this.updateUi();
1520
+ const context = (_b = this.nextSelectionChangeContext) !== null && _b !== void 0 ? _b : this.buildCallbackContext((_c = this.activeStateRestoreOperation) !== null && _c !== void 0 ? _c : 'createMask', this.activeStateRestoreOperation === 'undo' ||
1521
+ this.activeStateRestoreOperation === 'redo');
1522
+ this.emitOptionCallback('onSelectionChange', [this.buildSelection(selected), context]);
1523
+ }
1524
+ buildMaskListContext() {
1525
+ return {
1526
+ canvas: this.canvas,
1527
+ getListElementId: () => this.elements.maskList,
1528
+ onMaskSelected: (mask) => this.handleSelectionChanged([mask]),
1529
+ };
1530
+ }
1531
+ updateMaskList() {
1532
+ renderMaskList(this.buildMaskListContext());
1533
+ }
1534
+ updateMaskListSelection(selectedMask) {
1535
+ updateMaskListSelection(this.buildMaskListContext(), selectedMask);
1536
+ }
1537
+ async mergeMasks() {
1538
+ if (!this.canvas)
1539
+ return;
1540
+ if (!this.canRunIdleOperation('mergeMasks'))
1541
+ return;
1542
+ const hasMasks = this.canvas.getObjects().some(isMaskObject);
1543
+ if (!hasMasks)
1544
+ return;
1545
+ const callbackContext = this.buildCallbackContext('mergeMasks', false);
1546
+ const operationToken = this.operationGuard.beginBusyOperation('mergeMasks');
1547
+ this.emitBusyChangeIfChanged(callbackContext);
1548
+ this.updateUi();
1549
+ try {
1550
+ const mergeMasksContext = this.buildMergeMasksContext(operationToken);
1551
+ await mergeMasksImpl(mergeMasksContext);
1552
+ this.updateInputs();
1553
+ this.updateMaskList();
1554
+ this.emitMasksChanged(callbackContext);
1555
+ this.emitImageChanged(callbackContext);
1556
+ }
1557
+ finally {
1558
+ this.operationGuard.endBusyOperation(operationToken);
1559
+ this.emitBusyChangeIfChanged(callbackContext);
1560
+ this.updateUi();
1561
+ }
1562
+ }
1563
+ downloadImage(fileName) {
1564
+ if (!this.canvas)
1565
+ return;
1566
+ if (!this.canRunIdleOperation('downloadImage'))
1567
+ return;
1568
+ const callbackContext = this.buildCallbackContext('downloadImage', false);
1569
+ const operationToken = this.operationGuard.beginBusyOperation('downloadImage');
1570
+ this.emitBusyChangeIfChanged(callbackContext);
1571
+ const exportContext = this.buildExportServiceContext();
1572
+ try {
1573
+ downloadImageImpl(exportContext, fileName);
1574
+ }
1575
+ finally {
1576
+ this.operationGuard.endBusyOperation(operationToken);
1577
+ this.emitBusyChangeIfChanged(callbackContext);
1578
+ }
1579
+ }
1580
+ async exportImageBase64(options) {
1581
+ if (!this.canvas)
1582
+ return '';
1583
+ if (!this.canRunIdleOperation('exportImageBase64', options))
1584
+ return '';
1585
+ const callbackContext = this.buildCallbackContext('exportImageBase64', false);
1586
+ const operationToken = this.operationGuard.beginBusyOperation('exportImageBase64');
1587
+ this.emitBusyChangeIfChanged(callbackContext);
1588
+ const exportContext = this.buildExportServiceContext();
1589
+ try {
1590
+ return await exportImageBase64Impl(exportContext, options);
1591
+ }
1592
+ finally {
1593
+ this.operationGuard.endBusyOperation(operationToken);
1594
+ this.emitBusyChangeIfChanged(callbackContext);
1595
+ }
1596
+ }
1597
+ async exportImageFile(options) {
1598
+ this.assertIdleForOperation('exportImageFile', options);
1599
+ const callbackContext = this.buildCallbackContext('exportImageFile', false);
1600
+ const operationToken = this.operationGuard.beginBusyOperation('exportImageFile');
1601
+ this.emitBusyChangeIfChanged(callbackContext);
1602
+ const exportContext = this.buildExportServiceContext();
1603
+ try {
1604
+ return await exportImageFileImpl(exportContext, options);
1605
+ }
1606
+ finally {
1607
+ this.operationGuard.endBusyOperation(operationToken);
1608
+ this.emitBusyChangeIfChanged(callbackContext);
1609
+ }
1610
+ }
1611
+ buildExportServiceContext() {
1612
+ return {
1613
+ fabric: this.fabricModule,
1614
+ canvas: this.canvas,
1615
+ options: this.options,
1616
+ isImageLoaded: () => this.isImageLoaded(),
1617
+ getOriginalImage: () => this.originalImage,
1618
+ };
1619
+ }
1620
+ buildMergeMasksContext(operationToken) {
1621
+ return {
1622
+ ...this.buildExportServiceContext(),
1623
+ historyManager: this.historyManager,
1624
+ containerElement: this.containerElement,
1625
+ loadImage: async (base64, providedOptions) => {
1626
+ const geometry = this.captureImageDisplayGeometry();
1627
+ await this.loadImageInternal(base64, this.withInternalOperationOptions(operationToken, providedOptions !== null && providedOptions !== void 0 ? providedOptions : {}));
1628
+ this.restoreMergedImageDisplayGeometry(geometry);
1629
+ },
1630
+ saveState: () => this.captureSnapshotInternal(),
1631
+ loadFromState: (snapshot) => this.loadFromStateInternal(snapshot, this.withInternalOperationOptions(operationToken, this.withAnimationQueueBypass())),
1632
+ removeAllMasksNoHistory: () => {
1633
+ const context = this.buildRemoveMaskContext();
1634
+ removeAllMasksImpl(context, { saveHistory: false });
1635
+ },
1636
+ };
1637
+ }
1638
+ captureSnapshotInternal() {
1639
+ var _a;
1640
+ if (!this.canvas) {
1641
+ throw new Error('[ImageEditor] Cannot capture canvas snapshot before init or after dispose.');
1642
+ }
1643
+ const activeMask = this.getActiveMaskForSnapshot();
1644
+ this.hideAllMaskLabels();
1645
+ return saveStateImpl({
1646
+ canvas: this.canvas,
1647
+ activeMaskId: (_a = activeMask === null || activeMask === void 0 ? void 0 : activeMask.maskId) !== null && _a !== void 0 ? _a : null,
1648
+ currentScale: this.currentScale,
1649
+ currentRotation: this.currentRotation,
1650
+ baseImageScale: this.baseImageScale,
1651
+ currentImageMimeType: this.currentImageMimeType,
1652
+ });
1653
+ }
1654
+ getActiveMaskForSnapshot() {
1655
+ var _a;
1656
+ if (!this.canvas)
1657
+ return null;
1658
+ const activeObject = this.canvas.getActiveObject();
1659
+ if (activeObject && isMaskObject(activeObject))
1660
+ return activeObject;
1661
+ const labeledMasks = this.canvas
1662
+ .getObjects()
1663
+ .filter((object) => isMaskObject(object) && !!object.labelObject);
1664
+ return labeledMasks.length === 1 ? ((_a = labeledMasks[0]) !== null && _a !== void 0 ? _a : null) : null;
1665
+ }
1666
+ enterMosaicMode() {
1667
+ if (!this.canvas || !this.originalImage)
1668
+ return;
1669
+ if (this.mosaicSession)
1670
+ return;
1671
+ if (!this.isImageLoaded())
1672
+ return;
1673
+ if (!this.canRunIdleOperation('enterMosaicMode'))
1674
+ return;
1675
+ enterMosaicModeImpl(this.buildMosaicControllerContext());
1676
+ this.updateInputs();
1677
+ this.updateUi();
1678
+ const callbackContext = this.buildCallbackContext('enterMosaicMode', false);
1679
+ this.emitBusyChangeIfChanged(callbackContext);
1680
+ this.emitImageChanged(callbackContext);
1681
+ }
1682
+ exitMosaicMode() {
1683
+ if (!this.canvas || !this.mosaicSession)
1684
+ return;
1685
+ if (!this.canRunIdleOperation('exitMosaicMode'))
1686
+ return;
1687
+ exitMosaicModeImpl(this.buildMosaicControllerContext());
1688
+ this.updateInputs();
1689
+ this.updateUi();
1690
+ const callbackContext = this.buildCallbackContext('exitMosaicMode', false);
1691
+ this.emitBusyChangeIfChanged(callbackContext);
1692
+ this.emitImageChanged(callbackContext);
1693
+ }
1694
+ isMosaicMode() {
1695
+ return this.mosaicSession !== null;
1696
+ }
1697
+ getMosaicConfig() {
1698
+ return cloneResolvedMosaicConfig(this.currentMosaicConfig);
1699
+ }
1700
+ setMosaicConfig(config) {
1701
+ this.applyMosaicConfigPatch(config, 'setMosaicConfig');
1702
+ }
1703
+ resetMosaicConfig() {
1704
+ if (this.isDisposed)
1705
+ return;
1706
+ const nextConfig = cloneResolvedMosaicConfig(this.defaultMosaicConfig);
1707
+ if (areResolvedMosaicConfigsEqual(this.currentMosaicConfig, nextConfig))
1708
+ return;
1709
+ this.currentMosaicConfig = nextConfig;
1710
+ if (this.mosaicSession && this.canvas) {
1711
+ updateMosaicPreview(this.buildMosaicControllerContext());
1712
+ }
1713
+ this.updateInputs();
1714
+ this.updateUi();
1715
+ this.emitImageChanged(this.buildCallbackContext('resetMosaicConfig', false));
1716
+ }
1717
+ setMosaicBrushSize(size) {
1718
+ this.applyMosaicConfigPatch({ brushSize: size }, 'setMosaicBrushSize');
1719
+ }
1720
+ setMosaicBlockSize(size) {
1721
+ this.applyMosaicConfigPatch({ blockSize: size }, 'setMosaicBlockSize');
1722
+ }
1723
+ applyMosaicConfigPatch(config, operation) {
1724
+ if (this.isDisposed)
1725
+ return;
1726
+ if (config === null || typeof config !== 'object' || Array.isArray(config)) {
1727
+ reportWarning(this.options, new TypeError('[ImageEditor] Invalid Mosaic config object.'), 'Ignored invalid Mosaic config.');
1728
+ return;
1729
+ }
1730
+ const invalidFields = getInvalidMosaicConfigFields(config);
1731
+ if (invalidFields.length > 0) {
1732
+ reportWarning(this.options, new TypeError(`[ImageEditor] Ignored invalid Mosaic config field(s): ` +
1733
+ `${invalidFields.join(', ')}.`), 'Ignored invalid Mosaic config fields.');
1734
+ }
1735
+ const nextConfig = mergeMosaicConfigPatch(this.currentMosaicConfig, config);
1736
+ if (areResolvedMosaicConfigsEqual(this.currentMosaicConfig, nextConfig))
1737
+ return;
1738
+ this.currentMosaicConfig = nextConfig;
1739
+ if (this.mosaicSession && this.canvas) {
1740
+ updateMosaicPreview(this.buildMosaicControllerContext());
1741
+ }
1742
+ this.updateInputs();
1743
+ this.updateUi();
1744
+ this.emitImageChanged(this.buildCallbackContext(operation, false));
1745
+ }
1746
+ buildMosaicControllerContext() {
1747
+ return {
1748
+ fabric: this.fabricModule,
1749
+ canvas: this.canvas,
1750
+ options: this.options,
1751
+ historyManager: this.historyManager,
1752
+ getMosaicConfig: () => cloneResolvedMosaicConfig(this.currentMosaicConfig),
1753
+ isImageLoaded: () => this.isImageLoaded(),
1754
+ getOriginalImage: () => this.originalImage,
1755
+ setOriginalImage: (image) => {
1756
+ this.originalImage = image;
1757
+ },
1758
+ getCurrentImageMimeType: () => this.currentImageMimeType,
1759
+ setCurrentImageMimeType: (mimeType) => {
1760
+ this.currentImageMimeType = mimeType;
1761
+ },
1762
+ getLastSnapshot: () => this.lastSnapshot,
1763
+ setLastSnapshot: (snapshot) => {
1764
+ this.lastSnapshot = snapshot;
1765
+ },
1766
+ captureSnapshot: () => this.captureSnapshotInternal(),
1767
+ loadFromState: (snapshot) => this.loadFromStateInternal(snapshot, this.withAnimationQueueBypass()),
1768
+ updateUi: () => {
1769
+ this.updateUi();
1770
+ },
1771
+ updateInputs: () => {
1772
+ this.updateInputs();
1773
+ },
1774
+ hideAllMaskLabels: () => {
1775
+ this.hideAllMaskLabels();
1776
+ },
1777
+ emitImageChanged: (context) => {
1778
+ this.emitImageChanged(context);
1779
+ },
1780
+ emitBusyChangeIfChanged: (context) => {
1781
+ this.emitBusyChangeIfChanged(context);
1782
+ },
1783
+ buildCallbackContext: (operation, isInternal) => this.buildCallbackContext(operation, isInternal),
1784
+ getMosaicSession: () => this.mosaicSession,
1785
+ setMosaicSession: (session) => {
1786
+ this.mosaicSession = session;
1787
+ },
1788
+ };
1789
+ }
1790
+ enterCropMode() {
1791
+ if (!this.canvas || !this.originalImage)
1792
+ return;
1793
+ if (this.cropSession)
1794
+ return;
1795
+ if (!this.isImageLoaded())
1796
+ return;
1797
+ if (!this.canRunIdleOperation('enterCropMode'))
1798
+ return;
1799
+ const cropControllerContext = this.buildCropControllerContext();
1800
+ enterCropModeImpl(cropControllerContext);
1801
+ this.updateUi();
1802
+ const callbackContext = this.buildCallbackContext('enterCropMode', false);
1803
+ this.emitBusyChangeIfChanged(callbackContext);
1804
+ this.emitImageChanged(callbackContext);
1805
+ }
1806
+ cancelCrop() {
1807
+ if (!this.canvas || !this.cropSession)
1808
+ return;
1809
+ if (!this.canRunIdleOperation('cancelCrop'))
1810
+ return;
1811
+ const cropControllerContext = this.buildCropControllerContext();
1812
+ cancelCropImpl(cropControllerContext);
1813
+ this.cropSession = null;
1814
+ this.updateUi();
1815
+ this.canvas.requestRenderAll();
1816
+ const callbackContext = this.buildCallbackContext('cancelCrop', false);
1817
+ this.emitBusyChangeIfChanged(callbackContext);
1818
+ this.emitImageChanged(callbackContext);
1819
+ }
1820
+ async applyCrop() {
1821
+ if (!this.canvas || !this.cropSession)
1822
+ return;
1823
+ if (!this.canRunIdleOperation('applyCrop'))
1824
+ return;
1825
+ const callbackContext = this.buildCallbackContext('applyCrop', false);
1826
+ const hadMasks = this.getMasks().length > 0;
1827
+ const operationToken = this.operationGuard.beginBusyOperation('applyCrop');
1828
+ this.emitBusyChangeIfChanged(callbackContext);
1829
+ this.updateUi();
1830
+ try {
1831
+ const cropControllerContext = this.buildCropControllerContext(operationToken);
1832
+ await applyCropImpl(cropControllerContext);
1833
+ this.updateInputs();
1834
+ this.updateMaskList();
1835
+ if (hadMasks || this.getMasks().length > 0) {
1836
+ this.emitMasksChanged(callbackContext);
1837
+ }
1838
+ this.emitImageChanged(callbackContext);
1839
+ }
1840
+ finally {
1841
+ this.operationGuard.endBusyOperation(operationToken);
1842
+ this.emitBusyChangeIfChanged(callbackContext);
1843
+ this.updateUi();
1844
+ }
1845
+ }
1846
+ buildCropControllerContext(operationToken) {
1847
+ return {
1848
+ fabric: this.fabricModule,
1849
+ canvas: this.canvas,
1850
+ options: this.options,
1851
+ historyManager: this.historyManager,
1852
+ isImageLoaded: () => this.isImageLoaded(),
1853
+ getOriginalImage: () => this.originalImage,
1854
+ getCurrentImageMimeType: () => this.currentImageMimeType,
1855
+ getCropSession: () => this.cropSession,
1856
+ setCropSession: (s) => {
1857
+ this.cropSession = s;
1858
+ },
1859
+ saveState: () => this.captureSnapshotInternal(),
1860
+ loadFromState: (snapshot) => this.loadFromStateInternal(snapshot, this.withInternalOperationOptions(operationToken, this.withAnimationQueueBypass())),
1861
+ loadImage: (base64, providedOptions) => this.loadImageInternal(base64, this.withInternalOperationOptions(operationToken, providedOptions !== null && providedOptions !== void 0 ? providedOptions : {})),
1862
+ getMaskCounter: () => this.maskCounter,
1863
+ setMaskCounter: (n) => {
1864
+ this.maskCounter = n;
1865
+ },
1866
+ updateMaskList: () => {
1867
+ this.updateMaskList();
1868
+ },
1869
+ };
1870
+ }
1871
+ updateInputs() {
1872
+ const scaleId = this.elements.scalePercentageInput;
1873
+ if (scaleId) {
1874
+ const scaleInputElement = document.getElementById(scaleId);
1875
+ if (scaleInputElement) {
1876
+ scaleInputElement.value = String(Math.round(this.currentScale * 100));
1877
+ }
1878
+ }
1879
+ const mosaicConfig = this.getMosaicConfig();
1880
+ const mosaicBrushSizeInputId = this.elements.mosaicBrushSizeInput;
1881
+ if (mosaicBrushSizeInputId) {
1882
+ const brushInput = document.getElementById(mosaicBrushSizeInputId);
1883
+ if (brushInput)
1884
+ brushInput.value = String(mosaicConfig.brushSize);
1885
+ }
1886
+ const mosaicBlockSizeInputId = this.elements.mosaicBlockSizeInput;
1887
+ if (mosaicBlockSizeInputId) {
1888
+ const blockInput = document.getElementById(mosaicBlockSizeInputId);
1889
+ if (blockInput)
1890
+ blockInput.value = String(mosaicConfig.blockSize);
1891
+ }
1892
+ }
1893
+ updateUi() {
1894
+ var _a;
1895
+ if (!this.canvas)
1896
+ return;
1897
+ const hasImage = !!this.originalImage;
1898
+ const masks = hasImage ? this.canvas.getObjects().filter(isMaskObject) : [];
1899
+ const hasMasks = masks.length > 0;
1900
+ const activeObject = this.canvas.getActiveObject();
1901
+ const hasSelectedMask = !!(activeObject && isMaskObject(activeObject));
1902
+ const isDefaultTransform = this.currentScale === 1 && this.currentRotation === 0;
1903
+ const canUndo = this.historyManager.canUndo();
1904
+ const canRedo = this.historyManager.canRedo();
1905
+ const isInCropMode = this.cropSession !== null;
1906
+ const isInMosaicMode = this.mosaicSession !== null;
1907
+ const isBusy = this.operationGuard.isBusy() || this.animQueue.isBusy();
1908
+ const isMosaicApplying = ((_a = this.mosaicSession) === null || _a === void 0 ? void 0 : _a.isApplying) === true;
1909
+ if (isInCropMode) {
1910
+ CROP_MODE_CONTROL_KEYS.forEach((key) => {
1911
+ this.setControlEnabled(key, !isBusy && CROP_MODE_ENABLED_KEYS.includes(key));
1912
+ });
1913
+ return;
1914
+ }
1915
+ if (isInMosaicMode) {
1916
+ MOSAIC_MODE_CONTROL_KEYS.forEach((key) => {
1917
+ this.setControlEnabled(key, !isBusy && !isMosaicApplying && MOSAIC_MODE_ENABLED_KEYS.includes(key));
1918
+ });
1919
+ this.setControlEnabled('imageInput', false);
1920
+ return;
1921
+ }
1922
+ this.setControlEnabled('scalePercentageInput', hasImage && !isBusy);
1923
+ this.setControlEnabled('rotateLeftDegreesInput', hasImage && !isBusy);
1924
+ this.setControlEnabled('rotateRightDegreesInput', hasImage && !isBusy);
1925
+ this.setControlEnabled('zoomInButton', hasImage && !isBusy && this.currentScale < this.options.maxScale);
1926
+ this.setControlEnabled('zoomOutButton', hasImage && !isBusy && this.currentScale > this.options.minScale);
1927
+ this.setControlEnabled('rotateLeftButton', hasImage && !isBusy);
1928
+ this.setControlEnabled('rotateRightButton', hasImage && !isBusy);
1929
+ this.setControlEnabled('createMaskButton', hasImage && !isBusy);
1930
+ this.setControlEnabled('removeSelectedMaskButton', hasSelectedMask && !isBusy);
1931
+ this.setControlEnabled('removeAllMasksButton', hasMasks && !isBusy);
1932
+ this.setControlEnabled('mergeMasksButton', hasImage && hasMasks && !isBusy);
1933
+ this.setControlEnabled('downloadImageButton', hasImage && !isBusy);
1934
+ this.setControlEnabled('resetImageTransformButton', hasImage && !isDefaultTransform && !isBusy);
1935
+ this.setControlEnabled('undoButton', hasImage && !isBusy && canUndo);
1936
+ this.setControlEnabled('redoButton', hasImage && !isBusy && canRedo);
1937
+ this.setControlEnabled('enterCropModeButton', hasImage && !isBusy);
1938
+ this.setControlEnabled('enterMosaicModeButton', hasImage && !isBusy);
1939
+ this.setControlEnabled('exitMosaicModeButton', false);
1940
+ this.setControlEnabled('mosaicBrushSizeInput', !this.isDisposed);
1941
+ this.setControlEnabled('mosaicBlockSizeInput', !this.isDisposed);
1942
+ this.setControlEnabled('imageInput', !isBusy);
1943
+ this.setControlEnabled('applyCropButton', false);
1944
+ this.setControlEnabled('cancelCropButton', false);
1945
+ }
1946
+ setControlEnabled(key, isEnabled) {
1947
+ var _a;
1948
+ const id = this.elements[key];
1949
+ if (!id)
1950
+ return;
1951
+ const controlElement = document.getElementById(id);
1952
+ if (!controlElement)
1953
+ return;
1954
+ this.recordElementOriginalState(key, controlElement);
1955
+ if ('disabled' in controlElement) {
1956
+ controlElement.disabled = !isEnabled;
1957
+ return;
1958
+ }
1959
+ if (!isEnabled) {
1960
+ controlElement.setAttribute('aria-disabled', 'true');
1961
+ controlElement.style.pointerEvents = 'none';
1962
+ }
1963
+ else {
1964
+ const originalAria = this.elementOriginalAriaDisabledMap.get(key);
1965
+ if (originalAria === null || originalAria === undefined) {
1966
+ controlElement.removeAttribute('aria-disabled');
1967
+ }
1968
+ else {
1969
+ controlElement.setAttribute('aria-disabled', originalAria);
1970
+ }
1971
+ controlElement.style.pointerEvents =
1972
+ (_a = this.elementOriginalPointerEventsMap.get(key)) !== null && _a !== void 0 ? _a : '';
1973
+ }
1974
+ }
1975
+ recordElementOriginalState(key, element) {
1976
+ if (!this.elementOriginalAriaDisabledMap.has(key)) {
1977
+ this.elementOriginalAriaDisabledMap.set(key, element.getAttribute('aria-disabled'));
1978
+ }
1979
+ if (!this.elementOriginalPointerEventsMap.has(key)) {
1980
+ this.elementOriginalPointerEventsMap.set(key, element.style.pointerEvents || '');
1981
+ }
1982
+ if ('disabled' in element && !this.elementOriginalDisabledMap.has(key)) {
1983
+ this.elementOriginalDisabledMap.set(key, !!element.disabled);
1984
+ }
1985
+ }
1986
+ restoreElementOriginalStates() {
1987
+ var _a, _b;
1988
+ for (const key of Object.keys(this.elements)) {
1989
+ const id = this.elements[key];
1990
+ if (!id)
1991
+ continue;
1992
+ const element = document.getElementById(id);
1993
+ if (!element)
1994
+ continue;
1995
+ if ('disabled' in element && this.elementOriginalDisabledMap.has(key)) {
1996
+ element.disabled =
1997
+ (_a = this.elementOriginalDisabledMap.get(key)) !== null && _a !== void 0 ? _a : false;
1998
+ }
1999
+ if (this.elementOriginalAriaDisabledMap.has(key)) {
2000
+ const originalAria = this.elementOriginalAriaDisabledMap.get(key);
2001
+ if (originalAria === null || originalAria === undefined) {
2002
+ element.removeAttribute('aria-disabled');
2003
+ }
2004
+ else {
2005
+ element.setAttribute('aria-disabled', originalAria);
2006
+ }
2007
+ }
2008
+ if (this.elementOriginalPointerEventsMap.has(key)) {
2009
+ element.style.pointerEvents = (_b = this.elementOriginalPointerEventsMap.get(key)) !== null && _b !== void 0 ? _b : '';
2010
+ }
2011
+ }
2012
+ this.elementOriginalDisabledMap.clear();
2013
+ this.elementOriginalAriaDisabledMap.clear();
2014
+ this.elementOriginalPointerEventsMap.clear();
2015
+ }
2016
+ updatePlaceholderStatus() {
2017
+ setPlaceholderVisibleImpl(this.placeholderElement, this.containerElement, this.options.showPlaceholder ? !this.originalImage : false);
2018
+ }
2019
+ dispose() {
2020
+ var _a;
2021
+ if (this.isDisposed)
2022
+ return;
2023
+ const context = this.buildCallbackContext('dispose', false);
2024
+ const previousImage = this.originalImage;
2025
+ this.isDisposed = true;
2026
+ this.operationGuard.markDisposed();
2027
+ this.animQueue.clear();
2028
+ (_a = this.domBindings) === null || _a === void 0 ? void 0 : _a.removeAll();
2029
+ this.restoreElementOriginalStates();
2030
+ if (this.cropSession && this.canvas) {
2031
+ try {
2032
+ const context = this.buildCropControllerContext();
2033
+ cancelCropImpl(context);
2034
+ }
2035
+ catch {
2036
+ }
2037
+ this.cropSession = null;
2038
+ }
2039
+ if (this.mosaicSession && this.canvas) {
2040
+ try {
2041
+ exitMosaicModeImpl(this.buildMosaicControllerContext());
2042
+ }
2043
+ catch {
2044
+ }
2045
+ this.mosaicSession = null;
2046
+ }
2047
+ if (this.canvas) {
2048
+ try {
2049
+ void Promise.resolve(this.canvas.dispose()).catch(() => {
2050
+ });
2051
+ }
2052
+ catch {
2053
+ }
2054
+ this.canvas = null;
2055
+ this.canvasElement = null;
2056
+ this.isImageLoadedToCanvas = false;
2057
+ }
2058
+ this.originalImage = null;
2059
+ this.currentImageMimeType = null;
2060
+ this.lastMask = null;
2061
+ this.maskCounter = 0;
2062
+ this.currentScale = 1;
2063
+ this.currentRotation = 0;
2064
+ this.baseImageScale = 1;
2065
+ this.lastSnapshot = null;
2066
+ this.transformController = null;
2067
+ this.viewportCache.clear();
2068
+ if (previousImage) {
2069
+ this.emitOptionCallback('onImageCleared', [previousImage, context]);
2070
+ }
2071
+ this.emitImageChanged(context);
2072
+ this.emitBusyChangeIfChanged(context);
2073
+ this.emitOptionCallback('onEditorDisposed', [context]);
2074
+ }
2075
+ }
2076
+ //# sourceMappingURL=image-editor.js.map