@bensitu/image-editor 2.0.0 → 2.2.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 (147) hide show
  1. package/README.md +346 -90
  2. package/dist/cjs/index.cjs +4883 -1191
  3. package/dist/cjs/index.cjs.map +1 -1
  4. package/dist/esm/animation/animation-queue.js +16 -9
  5. package/dist/esm/animation/animation-queue.js.map +1 -1
  6. package/dist/esm/annotation/annotation-lock.js +7 -0
  7. package/dist/esm/annotation/annotation-lock.js.map +1 -0
  8. package/dist/esm/annotation/annotation-manager.js +217 -0
  9. package/dist/esm/annotation/annotation-manager.js.map +1 -0
  10. package/dist/esm/annotation/annotation-style.js +50 -0
  11. package/dist/esm/annotation/annotation-style.js.map +1 -0
  12. package/dist/esm/annotation/draw-controller.js +114 -0
  13. package/dist/esm/annotation/draw-controller.js.map +1 -0
  14. package/dist/esm/annotation/text-controller.js +234 -0
  15. package/dist/esm/annotation/text-controller.js.map +1 -0
  16. package/dist/esm/core/default-options.js +447 -11
  17. package/dist/esm/core/default-options.js.map +1 -1
  18. package/dist/esm/core/editor-object-kind.js +37 -0
  19. package/dist/esm/core/editor-object-kind.js.map +1 -0
  20. package/dist/esm/core/errors.js +19 -0
  21. package/dist/esm/core/errors.js.map +1 -1
  22. package/dist/esm/core/layer-order.js +100 -0
  23. package/dist/esm/core/layer-order.js.map +1 -0
  24. package/dist/esm/core/operation-guard.js +28 -0
  25. package/dist/esm/core/operation-guard.js.map +1 -1
  26. package/dist/esm/core/public-types.js +34 -1
  27. package/dist/esm/core/public-types.js.map +1 -1
  28. package/dist/esm/core/state-serializer.js +108 -27
  29. package/dist/esm/core/state-serializer.js.map +1 -1
  30. package/dist/esm/crop/crop-controller.js +6 -2
  31. package/dist/esm/crop/crop-controller.js.map +1 -1
  32. package/dist/esm/export/export-format.js.map +1 -1
  33. package/dist/esm/export/export-service.js +140 -141
  34. package/dist/esm/export/export-service.js.map +1 -1
  35. package/dist/esm/export/overlay-merge-service.js +75 -0
  36. package/dist/esm/export/overlay-merge-service.js.map +1 -0
  37. package/dist/esm/fabric/fabric-animation.js +56 -4
  38. package/dist/esm/fabric/fabric-animation.js.map +1 -1
  39. package/dist/esm/history/history-manager.js +2 -2
  40. package/dist/esm/history/history-manager.js.map +1 -1
  41. package/dist/esm/image/image-loader.js +27 -65
  42. package/dist/esm/image/image-loader.js.map +1 -1
  43. package/dist/esm/image/image-resampler.js +7 -2
  44. package/dist/esm/image/image-resampler.js.map +1 -1
  45. package/dist/esm/image/layout-manager.js +2 -20
  46. package/dist/esm/image/layout-manager.js.map +1 -1
  47. package/dist/esm/image/transform-controller.js.map +1 -1
  48. package/dist/esm/image-editor.js +1474 -135
  49. package/dist/esm/image-editor.js.map +1 -1
  50. package/dist/esm/index.js +1 -1
  51. package/dist/esm/index.js.map +1 -1
  52. package/dist/esm/mask/mask-factory.js +92 -43
  53. package/dist/esm/mask/mask-factory.js.map +1 -1
  54. package/dist/esm/mask/mask-label-manager.js +2 -0
  55. package/dist/esm/mask/mask-label-manager.js.map +1 -1
  56. package/dist/esm/mask/mask-list.js +9 -3
  57. package/dist/esm/mask/mask-list.js.map +1 -1
  58. package/dist/esm/mask/mask-style.js.map +1 -1
  59. package/dist/esm/mosaic/mosaic-controller.js +666 -0
  60. package/dist/esm/mosaic/mosaic-controller.js.map +1 -0
  61. package/dist/esm/mosaic/mosaic-geometry.js +81 -0
  62. package/dist/esm/mosaic/mosaic-geometry.js.map +1 -0
  63. package/dist/esm/mosaic/mosaic-pixelate.js +71 -0
  64. package/dist/esm/mosaic/mosaic-pixelate.js.map +1 -0
  65. package/dist/esm/ui/dom-bindings.js +10 -3
  66. package/dist/esm/ui/dom-bindings.js.map +1 -1
  67. package/dist/esm/utils/image-element-loader.js +55 -0
  68. package/dist/esm/utils/image-element-loader.js.map +1 -0
  69. package/dist/esm/utils/number.js.map +1 -1
  70. package/dist/esm/utils/pointer.js +28 -0
  71. package/dist/esm/utils/pointer.js.map +1 -0
  72. package/dist/types/animation/animation-queue.d.ts.map +1 -1
  73. package/dist/types/annotation/annotation-lock.d.ts +12 -0
  74. package/dist/types/annotation/annotation-lock.d.ts.map +1 -0
  75. package/dist/types/annotation/annotation-manager.d.ts +33 -0
  76. package/dist/types/annotation/annotation-manager.d.ts.map +1 -0
  77. package/dist/types/annotation/annotation-style.d.ts +13 -0
  78. package/dist/types/annotation/annotation-style.d.ts.map +1 -0
  79. package/dist/types/annotation/draw-controller.d.ts +43 -0
  80. package/dist/types/annotation/draw-controller.d.ts.map +1 -0
  81. package/dist/types/annotation/text-controller.d.ts +47 -0
  82. package/dist/types/annotation/text-controller.d.ts.map +1 -0
  83. package/dist/types/core/default-options.d.ts +46 -6
  84. package/dist/types/core/default-options.d.ts.map +1 -1
  85. package/dist/types/core/editor-object-kind.d.ts +29 -0
  86. package/dist/types/core/editor-object-kind.d.ts.map +1 -0
  87. package/dist/types/core/errors.d.ts +12 -2
  88. package/dist/types/core/errors.d.ts.map +1 -1
  89. package/dist/types/core/layer-order.d.ts +21 -0
  90. package/dist/types/core/layer-order.d.ts.map +1 -0
  91. package/dist/types/core/operation-guard.d.ts +2 -0
  92. package/dist/types/core/operation-guard.d.ts.map +1 -1
  93. package/dist/types/core/public-types.d.ts +341 -33
  94. package/dist/types/core/public-types.d.ts.map +1 -1
  95. package/dist/types/core/state-serializer.d.ts +32 -5
  96. package/dist/types/core/state-serializer.d.ts.map +1 -1
  97. package/dist/types/crop/crop-controller.d.ts +6 -7
  98. package/dist/types/crop/crop-controller.d.ts.map +1 -1
  99. package/dist/types/export/export-format.d.ts +5 -33
  100. package/dist/types/export/export-format.d.ts.map +1 -1
  101. package/dist/types/export/export-service.d.ts +24 -15
  102. package/dist/types/export/export-service.d.ts.map +1 -1
  103. package/dist/types/export/overlay-merge-service.d.ts +38 -0
  104. package/dist/types/export/overlay-merge-service.d.ts.map +1 -0
  105. package/dist/types/fabric/fabric-animation.d.ts.map +1 -1
  106. package/dist/types/history/history-manager.d.ts +11 -14
  107. package/dist/types/history/history-manager.d.ts.map +1 -1
  108. package/dist/types/image/image-loader.d.ts +24 -21
  109. package/dist/types/image/image-loader.d.ts.map +1 -1
  110. package/dist/types/image/image-resampler.d.ts +2 -2
  111. package/dist/types/image/image-resampler.d.ts.map +1 -1
  112. package/dist/types/image/layout-manager.d.ts +5 -49
  113. package/dist/types/image/layout-manager.d.ts.map +1 -1
  114. package/dist/types/image/transform-controller.d.ts +6 -9
  115. package/dist/types/image/transform-controller.d.ts.map +1 -1
  116. package/dist/types/image-editor.d.ts +93 -14
  117. package/dist/types/image-editor.d.ts.map +1 -1
  118. package/dist/types/index.d.cts +3 -3
  119. package/dist/types/index.d.cts.map +1 -1
  120. package/dist/types/index.d.ts +3 -3
  121. package/dist/types/index.d.ts.map +1 -1
  122. package/dist/types/mask/mask-factory.d.ts +24 -21
  123. package/dist/types/mask/mask-factory.d.ts.map +1 -1
  124. package/dist/types/mask/mask-label-manager.d.ts +10 -9
  125. package/dist/types/mask/mask-label-manager.d.ts.map +1 -1
  126. package/dist/types/mask/mask-list.d.ts +11 -12
  127. package/dist/types/mask/mask-list.d.ts.map +1 -1
  128. package/dist/types/mask/mask-style.d.ts +19 -20
  129. package/dist/types/mask/mask-style.d.ts.map +1 -1
  130. package/dist/types/mosaic/mosaic-controller.d.ts +82 -0
  131. package/dist/types/mosaic/mosaic-controller.d.ts.map +1 -0
  132. package/dist/types/mosaic/mosaic-geometry.d.ts +29 -0
  133. package/dist/types/mosaic/mosaic-geometry.d.ts.map +1 -0
  134. package/dist/types/mosaic/mosaic-pixelate.d.ts +23 -0
  135. package/dist/types/mosaic/mosaic-pixelate.d.ts.map +1 -0
  136. package/dist/types/ui/dom-bindings.d.ts +3 -1
  137. package/dist/types/ui/dom-bindings.d.ts.map +1 -1
  138. package/dist/types/ui/visibility-state.d.ts +2 -2
  139. package/dist/types/utils/image-element-loader.d.ts +19 -0
  140. package/dist/types/utils/image-element-loader.d.ts.map +1 -0
  141. package/dist/types/utils/number.d.ts +1 -2
  142. package/dist/types/utils/number.d.ts.map +1 -1
  143. package/dist/types/utils/pointer.d.ts +16 -0
  144. package/dist/types/utils/pointer.d.ts.map +1 -0
  145. package/dist/umd/image-editor.umd.js +1 -1
  146. package/dist/umd/image-editor.umd.js.map +1 -1
  147. package/package.json +1 -1
@@ -1,15 +1,22 @@
1
1
  import { AnimationQueue } from './animation/animation-queue.js';
2
2
  import { reportError, reportWarning } from './core/callback-reporter.js';
3
- import { resolveOptions } from './core/default-options.js';
3
+ import { areResolvedMosaicConfigsEqual, areResolvedDrawConfigsEqual, areResolvedTextAnnotationConfigsEqual, cloneResolvedMosaicConfig, cloneResolvedDrawConfig, cloneResolvedTextAnnotationConfig, getInvalidDrawConfigFields, getInvalidMosaicConfigFields, getInvalidTextAnnotationConfigFields, isLayoutMode, mergeDrawConfigPatch, mergeMosaicConfigPatch, mergeTextAnnotationConfigPatch, resolveOptions, } from './core/default-options.js';
4
4
  import { OperationGuard } from './core/operation-guard.js';
5
5
  import { loadFromState as loadFromStateImpl, saveState as saveStateImpl, } from './core/state-serializer.js';
6
6
  import { Command, HistoryManager } from './history/history-manager.js';
7
7
  import { detectFabric } from './fabric/fabric-adapter.js';
8
- import { isMaskObject } from './core/public-types.js';
8
+ import { isAnnotationObject, isDrawAnnotationObject, isEditableOverlayObject, isMaskObject, isTextAnnotationObject, } from './core/public-types.js';
9
+ import { getAnnotations as getAnnotationsImpl, removeAllAnnotations as removeAllAnnotationsImpl, removeAnnotationObjects, removeSelectedAnnotation as removeSelectedAnnotationImpl, renderAnnotationList, updateAnnotation as updateAnnotationImpl, updateAnnotationListSelection, updateSelectedAnnotation as updateSelectedAnnotationImpl, } from './annotation/annotation-manager.js';
10
+ import { attachTextEditingHandlersToAnnotations, createTextAnnotation as createTextAnnotationImpl, enterTextMode as enterTextModeImpl, exitTextMode as exitTextModeImpl, finalizeActiveTextEditing, } from './annotation/text-controller.js';
11
+ import { enterDrawMode as enterDrawModeImpl, exitDrawMode as exitDrawModeImpl, updateDrawBrush, } from './annotation/draw-controller.js';
12
+ import { isAnnotationLocked, isAnnotationUnlocked } from './annotation/annotation-lock.js';
13
+ import { syncAnnotationRuntimeStates } from './annotation/annotation-style.js';
14
+ import { normalizeLayerOrder, getEditableOverlayRange } from './core/layer-order.js';
9
15
  import { applyCrop as applyCropImpl, cancelCrop as cancelCropImpl, enterCropMode as enterCropModeImpl, } from './crop/crop-controller.js';
10
- import { downloadImage as downloadImageImpl, exportImageBase64 as exportImageBase64Impl, exportImageFile as exportImageFileImpl, mergeMasks as mergeMasksImpl, } from './export/export-service.js';
16
+ import { enterMosaicMode as enterMosaicModeImpl, exitMosaicMode as exitMosaicModeImpl, updateMosaicPreview, } from './mosaic/mosaic-controller.js';
17
+ import { downloadImage as downloadImageImpl, exportImageBase64 as exportImageBase64Impl, exportImageFile as exportImageFileImpl, mergeAnnotations as mergeAnnotationsImpl, mergeMasks as mergeMasksImpl, } from './export/export-service.js';
11
18
  import { loadImage as loadImageImpl } from './image/image-loader.js';
12
- import { ViewportCache, applyCanvasDimensions, computeScrollableCanvasSize, detectLayoutConflict, measureScrollbarSize, } from './image/layout-manager.js';
19
+ import { ViewportCache, applyCanvasDimensions, computeScrollableCanvasSize, measureScrollbarSize, } from './image/layout-manager.js';
13
20
  import { TransformController } from './image/transform-controller.js';
14
21
  import { createMask as createMaskImpl, removeAllMasks as removeAllMasksImpl, removeSelectedMask as removeSelectedMaskImpl, } from './mask/mask-factory.js';
15
22
  import { createLabelForMask, hideAllMaskLabels, removeLabelForMask, showLabelForMask, syncMaskLabel, } from './mask/mask-label-manager.js';
@@ -20,8 +27,8 @@ import { setPlaceholderVisible as setPlaceholderVisibleImpl } from './ui/visibil
20
27
  import { inferImageMimeType, readFileAsDataUrl, resetFileInput } from './utils/file.js';
21
28
  import { detectSourceMimeType } from './image/image-resampler.js';
22
29
  const LAYOUT_EPSILON = 0.5;
23
- const INTERNAL_OPERATION_TOKEN = Symbol.for('ImageEditorInternalOperation');
24
- const INTERNAL_ALLOW_DURING_ANIMATION_QUEUE = Symbol.for('ImageEditorAllowDuringAnimationQueue');
30
+ const INTERNAL_OPERATION_TOKEN = Symbol('ImageEditorInternalOperation');
31
+ const INTERNAL_ALLOW_DURING_ANIMATION_QUEUE = Symbol('ImageEditorAllowDuringAnimationQueue');
25
32
  const CROP_MODE_CONTROL_KEYS = [
26
33
  'scalePercentageInput',
27
34
  'rotateLeftDegreesInput',
@@ -32,6 +39,22 @@ const CROP_MODE_CONTROL_KEYS = [
32
39
  'removeSelectedMaskButton',
33
40
  'removeAllMasksButton',
34
41
  'mergeMasksButton',
42
+ 'mergeAnnotationsButton',
43
+ 'enterTextModeButton',
44
+ 'exitTextModeButton',
45
+ 'textColorInput',
46
+ 'textFontSizeInput',
47
+ 'enterDrawModeButton',
48
+ 'exitDrawModeButton',
49
+ 'drawColorInput',
50
+ 'drawBrushSizeInput',
51
+ 'removeSelectedAnnotationButton',
52
+ 'removeAllAnnotationsButton',
53
+ 'deleteSelectedObjectButton',
54
+ 'bringSelectedObjectForwardButton',
55
+ 'sendSelectedObjectBackwardButton',
56
+ 'bringSelectedObjectToFrontButton',
57
+ 'sendSelectedObjectToBackButton',
35
58
  'downloadImageButton',
36
59
  'zoomInButton',
37
60
  'zoomOutButton',
@@ -42,9 +65,155 @@ const CROP_MODE_CONTROL_KEYS = [
42
65
  'enterCropModeButton',
43
66
  'applyCropButton',
44
67
  'cancelCropButton',
68
+ 'enterMosaicModeButton',
69
+ 'exitMosaicModeButton',
70
+ 'mosaicBrushSizeInput',
71
+ 'mosaicBlockSizeInput',
45
72
  ];
46
73
  const CROP_MODE_ENABLED_KEYS = ['applyCropButton', 'cancelCropButton'];
47
74
  const CROP_SESSION_ALLOWED_OPERATIONS = new Set(['applyCrop', 'cancelCrop']);
75
+ const TEXT_MODE_ENABLED_KEYS = [
76
+ 'exitTextModeButton',
77
+ 'textColorInput',
78
+ 'textFontSizeInput',
79
+ ];
80
+ const DRAW_MODE_ENABLED_KEYS = [
81
+ 'exitDrawModeButton',
82
+ 'drawColorInput',
83
+ 'drawBrushSizeInput',
84
+ ];
85
+ const MOSAIC_MODE_CONTROL_KEYS = [
86
+ 'scalePercentageInput',
87
+ 'rotateLeftDegreesInput',
88
+ 'rotateRightDegreesInput',
89
+ 'rotateLeftButton',
90
+ 'rotateRightButton',
91
+ 'createMaskButton',
92
+ 'removeSelectedMaskButton',
93
+ 'removeAllMasksButton',
94
+ 'mergeMasksButton',
95
+ 'mergeAnnotationsButton',
96
+ 'enterTextModeButton',
97
+ 'exitTextModeButton',
98
+ 'textColorInput',
99
+ 'textFontSizeInput',
100
+ 'enterDrawModeButton',
101
+ 'exitDrawModeButton',
102
+ 'drawColorInput',
103
+ 'drawBrushSizeInput',
104
+ 'removeSelectedAnnotationButton',
105
+ 'removeAllAnnotationsButton',
106
+ 'deleteSelectedObjectButton',
107
+ 'bringSelectedObjectForwardButton',
108
+ 'sendSelectedObjectBackwardButton',
109
+ 'bringSelectedObjectToFrontButton',
110
+ 'sendSelectedObjectToBackButton',
111
+ 'downloadImageButton',
112
+ 'zoomInButton',
113
+ 'zoomOutButton',
114
+ 'resetImageTransformButton',
115
+ 'undoButton',
116
+ 'redoButton',
117
+ 'imageInput',
118
+ 'enterCropModeButton',
119
+ 'applyCropButton',
120
+ 'cancelCropButton',
121
+ 'enterMosaicModeButton',
122
+ 'exitMosaicModeButton',
123
+ 'mosaicBrushSizeInput',
124
+ 'mosaicBlockSizeInput',
125
+ ];
126
+ const MOSAIC_MODE_ENABLED_KEYS = [
127
+ 'exitMosaicModeButton',
128
+ 'mosaicBrushSizeInput',
129
+ 'mosaicBlockSizeInput',
130
+ ];
131
+ const MOSAIC_SESSION_ALLOWED_OPERATIONS = new Set([
132
+ 'exitMosaicMode',
133
+ 'applyMosaic',
134
+ 'setMosaicConfig',
135
+ 'resetMosaicConfig',
136
+ 'setMosaicBrushSize',
137
+ 'setMosaicBlockSize',
138
+ 'saveState',
139
+ ]);
140
+ const SCROLLBAR_SETTLE_EPSILON = 1;
141
+ const IMAGE_EDITOR_OPERATIONS = new Set([
142
+ 'init',
143
+ 'loadImage',
144
+ 'loadFromState',
145
+ 'saveState',
146
+ 'scaleImage',
147
+ 'rotateImage',
148
+ 'resetImageTransform',
149
+ 'createMask',
150
+ 'removeSelectedMask',
151
+ 'removeAllMasks',
152
+ 'mergeMasks',
153
+ 'createTextAnnotation',
154
+ 'enterTextMode',
155
+ 'exitTextMode',
156
+ 'setTextConfig',
157
+ 'resetTextConfig',
158
+ 'setTextColor',
159
+ 'setTextFontSize',
160
+ 'enterDrawMode',
161
+ 'exitDrawMode',
162
+ 'setDrawConfig',
163
+ 'resetDrawConfig',
164
+ 'setDrawColor',
165
+ 'setDrawBrushSize',
166
+ 'updateSelectedAnnotation',
167
+ 'updateAnnotation',
168
+ 'removeSelectedAnnotation',
169
+ 'removeAllAnnotations',
170
+ 'deleteSelectedObject',
171
+ 'mergeAnnotations',
172
+ 'bringSelectedObjectForward',
173
+ 'sendSelectedObjectBackward',
174
+ 'bringSelectedObjectToFront',
175
+ 'sendSelectedObjectToBack',
176
+ 'enterCropMode',
177
+ 'applyCrop',
178
+ 'cancelCrop',
179
+ 'enterMosaicMode',
180
+ 'exitMosaicMode',
181
+ 'applyMosaic',
182
+ 'setMosaicConfig',
183
+ 'resetMosaicConfig',
184
+ 'setMosaicBrushSize',
185
+ 'setMosaicBlockSize',
186
+ 'undo',
187
+ 'redo',
188
+ 'exportImageBase64',
189
+ 'exportImageFile',
190
+ 'downloadImage',
191
+ 'dispose',
192
+ ]);
193
+ const TOOL_MODE_ALLOWED_OPERATIONS = {
194
+ crop: CROP_SESSION_ALLOWED_OPERATIONS,
195
+ mosaic: MOSAIC_SESSION_ALLOWED_OPERATIONS,
196
+ text: new Set([
197
+ 'exitTextMode',
198
+ 'createTextAnnotation',
199
+ 'setTextConfig',
200
+ 'resetTextConfig',
201
+ 'setTextColor',
202
+ 'setTextFontSize',
203
+ 'saveState',
204
+ ]),
205
+ draw: new Set([
206
+ 'exitDrawMode',
207
+ 'setDrawConfig',
208
+ 'resetDrawConfig',
209
+ 'setDrawColor',
210
+ 'setDrawBrushSize',
211
+ 'saveState',
212
+ ]),
213
+ };
214
+ function isImageEditorOperation(value) {
215
+ return value !== null && IMAGE_EDITOR_OPERATIONS.has(value);
216
+ }
48
217
  export class ImageEditor {
49
218
  constructor(fabricModuleOrOptions = {}, options = {}) {
50
219
  var _a;
@@ -66,6 +235,48 @@ export class ImageEditor {
66
235
  writable: true,
67
236
  value: void 0
68
237
  });
238
+ Object.defineProperty(this, "currentLayoutMode", {
239
+ enumerable: true,
240
+ configurable: true,
241
+ writable: true,
242
+ value: 'expand'
243
+ });
244
+ Object.defineProperty(this, "defaultMosaicConfig", {
245
+ enumerable: true,
246
+ configurable: true,
247
+ writable: true,
248
+ value: void 0
249
+ });
250
+ Object.defineProperty(this, "currentMosaicConfig", {
251
+ enumerable: true,
252
+ configurable: true,
253
+ writable: true,
254
+ value: void 0
255
+ });
256
+ Object.defineProperty(this, "defaultTextConfig", {
257
+ enumerable: true,
258
+ configurable: true,
259
+ writable: true,
260
+ value: void 0
261
+ });
262
+ Object.defineProperty(this, "currentTextConfig", {
263
+ enumerable: true,
264
+ configurable: true,
265
+ writable: true,
266
+ value: void 0
267
+ });
268
+ Object.defineProperty(this, "defaultDrawConfig", {
269
+ enumerable: true,
270
+ configurable: true,
271
+ writable: true,
272
+ value: void 0
273
+ });
274
+ Object.defineProperty(this, "currentDrawConfig", {
275
+ enumerable: true,
276
+ configurable: true,
277
+ writable: true,
278
+ value: void 0
279
+ });
69
280
  Object.defineProperty(this, "canvas", {
70
281
  enumerable: true,
71
282
  configurable: true,
@@ -162,6 +373,12 @@ export class ImageEditor {
162
373
  writable: true,
163
374
  value: null
164
375
  });
376
+ Object.defineProperty(this, "annotationCounter", {
377
+ enumerable: true,
378
+ configurable: true,
379
+ writable: true,
380
+ value: 0
381
+ });
165
382
  Object.defineProperty(this, "lastSnapshot", {
166
383
  enumerable: true,
167
384
  configurable: true,
@@ -204,12 +421,42 @@ export class ImageEditor {
204
421
  writable: true,
205
422
  value: null
206
423
  });
424
+ Object.defineProperty(this, "mosaicSession", {
425
+ enumerable: true,
426
+ configurable: true,
427
+ writable: true,
428
+ value: null
429
+ });
430
+ Object.defineProperty(this, "textSession", {
431
+ enumerable: true,
432
+ configurable: true,
433
+ writable: true,
434
+ value: null
435
+ });
436
+ Object.defineProperty(this, "drawSession", {
437
+ enumerable: true,
438
+ configurable: true,
439
+ writable: true,
440
+ value: null
441
+ });
207
442
  Object.defineProperty(this, "domBindings", {
208
443
  enumerable: true,
209
444
  configurable: true,
210
445
  writable: true,
211
446
  value: null
212
447
  });
448
+ Object.defineProperty(this, "keyboardDocument", {
449
+ enumerable: true,
450
+ configurable: true,
451
+ writable: true,
452
+ value: null
453
+ });
454
+ Object.defineProperty(this, "keyboardHandler", {
455
+ enumerable: true,
456
+ configurable: true,
457
+ writable: true,
458
+ value: null
459
+ });
213
460
  Object.defineProperty(this, "isDisposed", {
214
461
  enumerable: true,
215
462
  configurable: true,
@@ -244,9 +491,19 @@ export class ImageEditor {
244
491
  this.fabricModule = (_a = detected.fabric) !== null && _a !== void 0 ? _a : {};
245
492
  this.isFabricLoaded = detected.isFabricLoaded;
246
493
  this.options = resolveOptions(detected.options);
247
- const layoutConflict = detectLayoutConflict(this.options);
248
- if (layoutConflict) {
249
- reportWarning(this.options, null, layoutConflict.message);
494
+ this.currentLayoutMode = this.options.layoutMode;
495
+ this.defaultMosaicConfig = this.options.defaultMosaicConfig;
496
+ this.currentMosaicConfig = cloneResolvedMosaicConfig(this.defaultMosaicConfig);
497
+ this.defaultTextConfig = this.options.defaultTextConfig;
498
+ this.currentTextConfig = cloneResolvedTextAnnotationConfig(this.defaultTextConfig);
499
+ this.defaultDrawConfig = this.options.defaultDrawConfig;
500
+ this.currentDrawConfig = cloneResolvedDrawConfig(this.defaultDrawConfig);
501
+ const rawDefaultLayoutMode = detected.options
502
+ .defaultLayoutMode;
503
+ if (rawDefaultLayoutMode !== undefined && !isLayoutMode(rawDefaultLayoutMode)) {
504
+ reportWarning(this.options, new TypeError(`[ImageEditor] Unsupported defaultLayoutMode ` +
505
+ `${JSON.stringify(rawDefaultLayoutMode)}. ` +
506
+ 'Expected "fit", "cover", or "expand".'), 'Invalid defaultLayoutMode fell back to "expand".');
250
507
  }
251
508
  this.operationGuard = new OperationGuard();
252
509
  this.animQueue = new AnimationQueue();
@@ -277,6 +534,23 @@ export class ImageEditor {
277
534
  removeSelectedMaskButton: 'removeSelectedMaskButton',
278
535
  removeAllMasksButton: 'removeAllMasksButton',
279
536
  mergeMasksButton: 'mergeMasksButton',
537
+ annotationList: 'annotationList',
538
+ enterTextModeButton: 'enterTextModeButton',
539
+ exitTextModeButton: 'exitTextModeButton',
540
+ textColorInput: 'textColorInput',
541
+ textFontSizeInput: 'textFontSizeInput',
542
+ enterDrawModeButton: 'enterDrawModeButton',
543
+ exitDrawModeButton: 'exitDrawModeButton',
544
+ drawColorInput: 'drawColorInput',
545
+ drawBrushSizeInput: 'drawBrushSizeInput',
546
+ removeSelectedAnnotationButton: 'removeSelectedAnnotationButton',
547
+ removeAllAnnotationsButton: 'removeAllAnnotationsButton',
548
+ deleteSelectedObjectButton: 'deleteSelectedObjectButton',
549
+ mergeAnnotationsButton: 'mergeAnnotationsButton',
550
+ bringSelectedObjectForwardButton: 'bringSelectedObjectForwardButton',
551
+ sendSelectedObjectBackwardButton: 'sendSelectedObjectBackwardButton',
552
+ bringSelectedObjectToFrontButton: 'bringSelectedObjectToFrontButton',
553
+ sendSelectedObjectToBackButton: 'sendSelectedObjectToBackButton',
280
554
  downloadImageButton: 'downloadImageButton',
281
555
  maskList: 'maskList',
282
556
  zoomInButton: 'zoomInButton',
@@ -288,15 +562,20 @@ export class ImageEditor {
288
562
  enterCropModeButton: 'enterCropModeButton',
289
563
  applyCropButton: 'applyCropButton',
290
564
  cancelCropButton: 'cancelCropButton',
565
+ enterMosaicModeButton: 'enterMosaicModeButton',
566
+ exitMosaicModeButton: 'exitMosaicModeButton',
567
+ mosaicBrushSizeInput: 'mosaicBrushSizeInput',
568
+ mosaicBlockSizeInput: 'mosaicBlockSizeInput',
291
569
  uploadArea: 'uploadArea',
292
570
  };
293
571
  this.elements = { ...defaults, ...idMap };
294
- this.domBindings = new DomBindings((key) => this.elements[key], () => this.isDisposed);
295
572
  this.initCanvas();
573
+ 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; });
296
574
  this.transformController = new TransformController(this.buildTransformContext());
297
575
  this.bindDomEvents();
298
576
  this.updateInputs();
299
577
  this.updateMaskList();
578
+ this.updateAnnotationList();
300
579
  this.updateUi();
301
580
  if (this.options.initialImageBase64) {
302
581
  void this.loadImage(this.options.initialImageBase64).catch(() => {
@@ -348,20 +627,24 @@ export class ImageEditor {
348
627
  });
349
628
  this.canvas.on('selection:cleared', () => this.handleSelectionChanged([]));
350
629
  const onObjectEvent = (e) => {
351
- if (e.target && isMaskObject(e.target))
352
- this.syncMaskLabel(e.target);
630
+ if (e.target)
631
+ this.handleObjectMovingScalingRotating(e.target);
353
632
  };
354
633
  const onObjectModified = (e) => {
355
- if (!e.target || !isMaskObject(e.target))
356
- return;
357
- this.syncMaskLabel(e.target);
358
- this.saveState();
634
+ if (e.target)
635
+ this.handleObjectModified(e.target);
359
636
  };
360
637
  this.canvas.on('object:moving', onObjectEvent);
361
638
  this.canvas.on('object:scaling', onObjectEvent);
362
639
  this.canvas.on('object:rotating', onObjectEvent);
363
640
  this.canvas.on('object:modified', onObjectModified);
364
641
  }
642
+ getLiveCanvasOrThrow(operationName) {
643
+ if (this.isDisposed || !this.canvas) {
644
+ throw new Error(`[ImageEditor] Cannot run "${operationName}" after dispose.`);
645
+ }
646
+ return this.canvas;
647
+ }
365
648
  bindDomEvents() {
366
649
  this.bindElementIfExists('uploadArea', 'click', () => {
367
650
  var _a;
@@ -396,6 +679,42 @@ export class ImageEditor {
396
679
  this.bindElementIfExists('mergeMasksButton', 'click', () => {
397
680
  void this.mergeMasks();
398
681
  });
682
+ this.bindElementIfExists('mergeAnnotationsButton', 'click', () => {
683
+ void this.mergeAnnotations();
684
+ });
685
+ this.bindElementIfExists('enterTextModeButton', 'click', () => {
686
+ this.enterTextMode();
687
+ });
688
+ this.bindElementIfExists('exitTextModeButton', 'click', () => {
689
+ this.exitTextMode();
690
+ });
691
+ this.bindElementIfExists('enterDrawModeButton', 'click', () => {
692
+ this.enterDrawMode();
693
+ });
694
+ this.bindElementIfExists('exitDrawModeButton', 'click', () => {
695
+ this.exitDrawMode();
696
+ });
697
+ this.bindElementIfExists('removeSelectedAnnotationButton', 'click', () => {
698
+ this.removeSelectedAnnotation();
699
+ });
700
+ this.bindElementIfExists('removeAllAnnotationsButton', 'click', () => {
701
+ this.removeAllAnnotations();
702
+ });
703
+ this.bindElementIfExists('deleteSelectedObjectButton', 'click', () => {
704
+ this.deleteSelectedObject();
705
+ });
706
+ this.bindElementIfExists('bringSelectedObjectForwardButton', 'click', () => {
707
+ this.bringSelectedObjectForward();
708
+ });
709
+ this.bindElementIfExists('sendSelectedObjectBackwardButton', 'click', () => {
710
+ this.sendSelectedObjectBackward();
711
+ });
712
+ this.bindElementIfExists('bringSelectedObjectToFrontButton', 'click', () => {
713
+ this.bringSelectedObjectToFront();
714
+ });
715
+ this.bindElementIfExists('sendSelectedObjectToBackButton', 'click', () => {
716
+ this.sendSelectedObjectToBack();
717
+ });
399
718
  this.bindElementIfExists('downloadImageButton', 'click', () => {
400
719
  this.downloadImage();
401
720
  });
@@ -442,11 +761,112 @@ export class ImageEditor {
442
761
  this.bindElementIfExists('cancelCropButton', 'click', () => {
443
762
  this.cancelCrop();
444
763
  });
764
+ this.bindElementIfExists('enterMosaicModeButton', 'click', () => {
765
+ this.enterMosaicMode();
766
+ });
767
+ this.bindElementIfExists('exitMosaicModeButton', 'click', () => {
768
+ this.exitMosaicMode();
769
+ });
770
+ const bindMosaicSizeInput = (key, applyValue) => {
771
+ const handler = (event) => {
772
+ const parsed = parseFloat(event.target.value);
773
+ applyValue(parsed);
774
+ };
775
+ this.bindElementIfExists(key, 'input', handler);
776
+ this.bindElementIfExists(key, 'change', handler);
777
+ };
778
+ bindMosaicSizeInput('mosaicBrushSizeInput', (value) => {
779
+ this.setMosaicBrushSize(value);
780
+ });
781
+ bindMosaicSizeInput('mosaicBlockSizeInput', (value) => {
782
+ this.setMosaicBlockSize(value);
783
+ });
784
+ const bindStringInput = (key, applyValue) => {
785
+ const handler = (event) => {
786
+ applyValue(event.target.value);
787
+ };
788
+ this.bindElementIfExists(key, 'input', handler);
789
+ this.bindElementIfExists(key, 'change', handler);
790
+ };
791
+ const bindNumberInput = (key, applyValue) => {
792
+ const handler = (event) => {
793
+ applyValue(parseFloat(event.target.value));
794
+ };
795
+ this.bindElementIfExists(key, 'input', handler);
796
+ this.bindElementIfExists(key, 'change', handler);
797
+ };
798
+ bindStringInput('textColorInput', (value) => this.applyTextColorInput(value));
799
+ bindNumberInput('textFontSizeInput', (value) => this.applyTextFontSizeInput(value));
800
+ bindStringInput('drawColorInput', (value) => this.applyDrawColorInput(value));
801
+ bindNumberInput('drawBrushSizeInput', (value) => this.applyDrawBrushSizeInput(value));
802
+ this.bindKeyboardEvents();
445
803
  }
446
804
  bindElementIfExists(key, event, handler) {
447
805
  var _a;
448
806
  (_a = this.domBindings) === null || _a === void 0 ? void 0 : _a.bindIfExists(key, event, handler);
449
807
  }
808
+ bindKeyboardEvents() {
809
+ var _a, _b;
810
+ const ownerDocument = (_b = (_a = this.canvasElement) === null || _a === void 0 ? void 0 : _a.ownerDocument) !== null && _b !== void 0 ? _b : document;
811
+ if (this.keyboardHandler && this.keyboardDocument) {
812
+ this.keyboardDocument.removeEventListener('keydown', this.keyboardHandler);
813
+ }
814
+ this.keyboardDocument = ownerDocument;
815
+ this.keyboardHandler = (event) => this.handleKeyboardEvent(event);
816
+ ownerDocument.addEventListener('keydown', this.keyboardHandler);
817
+ }
818
+ isNativeTextInputActive() {
819
+ var _a;
820
+ const activeElement = (_a = this.keyboardDocument) === null || _a === void 0 ? void 0 : _a.activeElement;
821
+ if (!activeElement)
822
+ return false;
823
+ const tagName = activeElement.tagName.toLowerCase();
824
+ return (tagName === 'input' ||
825
+ tagName === 'textarea' ||
826
+ tagName === 'select' ||
827
+ activeElement.isContentEditable === true);
828
+ }
829
+ isFabricTextEditingActive() {
830
+ var _a;
831
+ const activeObject = (_a = this.canvas) === null || _a === void 0 ? void 0 : _a.getActiveObject();
832
+ return !!(activeObject &&
833
+ isTextAnnotationObject(activeObject) &&
834
+ activeObject.isEditing === true);
835
+ }
836
+ handleKeyboardEvent(event) {
837
+ if (this.isDisposed)
838
+ return;
839
+ if (event.key === 'Delete' || event.key === 'Backspace') {
840
+ if (this.isNativeTextInputActive() || this.isFabricTextEditingActive())
841
+ return;
842
+ this.deleteSelectedObject();
843
+ return;
844
+ }
845
+ if (event.key !== 'Escape')
846
+ return;
847
+ if (this.isFabricTextEditingActive() && this.canvas) {
848
+ finalizeActiveTextEditing(this.buildTextControllerContext(), { commit: false });
849
+ event.preventDefault();
850
+ return;
851
+ }
852
+ if (this.textSession) {
853
+ this.exitTextMode();
854
+ }
855
+ else if (this.drawSession) {
856
+ this.exitDrawMode();
857
+ }
858
+ else if (this.mosaicSession) {
859
+ this.exitMosaicMode();
860
+ }
861
+ else if (this.cropSession) {
862
+ this.cancelCrop();
863
+ }
864
+ }
865
+ finalizeActiveTextEditingIfNeeded() {
866
+ if (!this.canvas || !this.isFabricTextEditingActive())
867
+ return;
868
+ finalizeActiveTextEditing(this.buildTextControllerContext(), { commit: true });
869
+ }
450
870
  async loadImageFile(file) {
451
871
  const inputId = this.elements.imageInput;
452
872
  const inputEl = inputId
@@ -477,6 +897,9 @@ export class ImageEditor {
477
897
  }
478
898
  }
479
899
  async loadImage(base64, options = {}) {
900
+ return this.loadImageInternal(base64, options);
901
+ }
902
+ async loadImageInternal(base64, options = {}) {
480
903
  if (!this.isFabricLoaded || !this.canvas)
481
904
  return;
482
905
  if (this.isDisposed)
@@ -485,9 +908,11 @@ export class ImageEditor {
485
908
  return;
486
909
  if (!this.canRunIdleOperation('loadImage', options))
487
910
  return;
911
+ this.finalizeActiveTextEditingIfNeeded();
488
912
  const callbackContext = this.getOperationContext('loadImage', options);
489
913
  const previousImage = this.originalImage;
490
914
  const hadMasks = this.getMasks().length > 0;
915
+ const hadAnnotations = this.getAnnotations().length > 0;
491
916
  this.emitOptionCallback('onImageLoadStart', [callbackContext]);
492
917
  this.operationGuard.beginLoading();
493
918
  this.emitBusyChangeIfChanged(callbackContext);
@@ -496,7 +921,7 @@ export class ImageEditor {
496
921
  const loadImageContext = {
497
922
  fabric: this.fabricModule,
498
923
  canvas: this.canvas,
499
- options: this.options,
924
+ options: this.getRuntimeOptions(),
500
925
  containerElement: this.containerElement,
501
926
  placeholderElement: this.placeholderElement,
502
927
  viewportCache: this.viewportCache,
@@ -516,6 +941,10 @@ export class ImageEditor {
516
941
  setMaskCounter: (v) => {
517
942
  this.maskCounter = v;
518
943
  },
944
+ getAnnotationCounter: () => this.annotationCounter,
945
+ setAnnotationCounter: (v) => {
946
+ this.annotationCounter = v;
947
+ },
519
948
  getCurrentScale: () => this.currentScale,
520
949
  setCurrentScale: (v) => {
521
950
  this.currentScale = v;
@@ -548,6 +977,7 @@ export class ImageEditor {
548
977
  this.lastMask = null;
549
978
  this.updateInputs();
550
979
  this.updateMaskList();
980
+ this.updateAnnotationList();
551
981
  this.updateUi();
552
982
  if (previousImage && previousImage !== this.originalImage) {
553
983
  this.emitOptionCallback('onImageCleared', [previousImage, callbackContext]);
@@ -559,6 +989,9 @@ export class ImageEditor {
559
989
  if (hadMasks) {
560
990
  this.emitMasksChanged(callbackContext);
561
991
  }
992
+ if (hadAnnotations) {
993
+ this.emitAnnotationsChanged(callbackContext);
994
+ }
562
995
  this.emitImageChanged(callbackContext);
563
996
  }
564
997
  getInternalOperationToken(options) {
@@ -583,10 +1016,11 @@ export class ImageEditor {
583
1016
  assertIdleForOperation(operationName, options) {
584
1017
  const token = this.getInternalOperationToken(options);
585
1018
  this.operationGuard.assertIdleForOperation(operationName, token);
586
- if (this.cropSession &&
1019
+ const activeToolMode = this.getActiveToolMode();
1020
+ if (activeToolMode &&
587
1021
  !this.operationGuard.isOwnOperation(token) &&
588
- !CROP_SESSION_ALLOWED_OPERATIONS.has(operationName)) {
589
- throw new Error(`[ImageEditor] Cannot run "${operationName}" while crop mode is active.`);
1022
+ !TOOL_MODE_ALLOWED_OPERATIONS[activeToolMode].has(operationName)) {
1023
+ throw new Error(`[ImageEditor] Cannot run "${operationName}" while ${activeToolMode} mode is active.`);
590
1024
  }
591
1025
  if (this.animQueue.isBusy() && !this.canRunDuringAnimationQueue(options)) {
592
1026
  throw new Error(`[ImageEditor] Cannot run "${operationName}" while an animation is queued.`);
@@ -597,10 +1031,17 @@ export class ImageEditor {
597
1031
  this.assertIdleForOperation(operationName, options);
598
1032
  return true;
599
1033
  }
600
- catch {
1034
+ catch (error) {
1035
+ if (!this.isExpectedIdleGuardError(error, operationName)) {
1036
+ throw error;
1037
+ }
601
1038
  return false;
602
1039
  }
603
1040
  }
1041
+ isExpectedIdleGuardError(error, operationName) {
1042
+ return (error instanceof Error &&
1043
+ error.message.startsWith(`[ImageEditor] Cannot run "${operationName}" `));
1044
+ }
604
1045
  assertCanQueueAnimation(operationName, options) {
605
1046
  this.operationGuard.assertCanQueueAnimation(operationName, this.getInternalOperationToken(options));
606
1047
  }
@@ -612,17 +1053,23 @@ export class ImageEditor {
612
1053
  ((_b = this.originalImage.height) !== null && _b !== void 0 ? _b : 0) > 0);
613
1054
  }
614
1055
  isBusy() {
615
- return this.operationGuard.isBusy() || this.animQueue.isBusy() || this.cropSession !== null;
1056
+ return this.operationGuard.isBusy() || this.animQueue.isBusy() || this.isToolModeActive();
616
1057
  }
617
1058
  setLayoutMode(mode) {
618
- if (mode !== 'fit' && mode !== 'cover' && mode !== 'expand') {
1059
+ if (!isLayoutMode(mode)) {
619
1060
  reportWarning(this.options, new TypeError(`[ImageEditor] Unsupported layout mode ${JSON.stringify(mode)}. ` +
620
1061
  'Expected "fit", "cover", or "expand".'), 'Ignored invalid layout mode.');
621
1062
  return;
622
1063
  }
623
- this.options.fitImageToCanvas = mode === 'fit';
624
- this.options.coverImageToCanvas = mode === 'cover';
625
- this.options.expandCanvasToImage = mode === 'expand';
1064
+ this.currentLayoutMode = mode;
1065
+ }
1066
+ getRuntimeOptions() {
1067
+ if (this.currentLayoutMode === this.options.layoutMode)
1068
+ return this.options;
1069
+ return Object.freeze({
1070
+ ...this.options,
1071
+ layoutMode: this.currentLayoutMode,
1072
+ });
626
1073
  }
627
1074
  buildCallbackContext(operation, isInternalOperation = false) {
628
1075
  return { operation, isInternalOperation };
@@ -631,7 +1078,7 @@ export class ImageEditor {
631
1078
  const internal = this.getInternalOperationToken(options);
632
1079
  const activeOperation = this.operationGuard.activeOperationName();
633
1080
  if (internal && activeOperation) {
634
- return this.buildCallbackContext(activeOperation, true);
1081
+ return this.buildCallbackContext(isImageEditorOperation(activeOperation) ? activeOperation : fallback, true);
635
1082
  }
636
1083
  return this.buildCallbackContext(fallback, false);
637
1084
  }
@@ -681,11 +1128,35 @@ export class ImageEditor {
681
1128
  return [];
682
1129
  return this.canvas.getObjects().filter(isMaskObject).slice();
683
1130
  }
1131
+ getAnnotations() {
1132
+ if (!this.canvas)
1133
+ return [];
1134
+ return getAnnotationsImpl(this.canvas);
1135
+ }
684
1136
  getMaskCollectionSignature() {
685
1137
  return this.getMasks()
686
1138
  .map((mask) => `${mask.maskId}:${mask.maskName}`)
687
1139
  .join('|');
688
1140
  }
1141
+ getAnnotationCollectionSignature() {
1142
+ return this.getAnnotations()
1143
+ .map((annotation) => `${annotation.annotationId}:${annotation.annotationName}`)
1144
+ .join('|');
1145
+ }
1146
+ getActiveToolMode() {
1147
+ if (this.cropSession)
1148
+ return 'crop';
1149
+ if (this.mosaicSession)
1150
+ return 'mosaic';
1151
+ if (this.textSession)
1152
+ return 'text';
1153
+ if (this.drawSession)
1154
+ return 'draw';
1155
+ return null;
1156
+ }
1157
+ isToolModeActive() {
1158
+ return this.getActiveToolMode() !== null;
1159
+ }
689
1160
  getEditorState() {
690
1161
  const canvasWidth = this.canvas ? this.canvas.getWidth() : 0;
691
1162
  const canvasHeight = this.canvas ? this.canvas.getHeight() : 0;
@@ -694,10 +1165,15 @@ export class ImageEditor {
694
1165
  hasImage: image !== null,
695
1166
  image,
696
1167
  maskCount: this.getMasks().length,
1168
+ annotationCount: this.getAnnotations().length,
697
1169
  currentScale: this.currentScale,
698
1170
  currentRotation: this.currentRotation,
699
1171
  isBusy: this.isBusy(),
1172
+ activeToolMode: this.getActiveToolMode(),
700
1173
  isCropMode: this.cropSession !== null,
1174
+ isMosaicMode: this.mosaicSession !== null,
1175
+ isTextMode: this.textSession !== null,
1176
+ isDrawMode: this.drawSession !== null,
701
1177
  canUndo: this.historyManager.canUndo(),
702
1178
  canRedo: this.historyManager.canRedo(),
703
1179
  canvasWidth,
@@ -710,6 +1186,9 @@ export class ImageEditor {
710
1186
  emitMasksChanged(context) {
711
1187
  this.emitOptionCallback('onMasksChanged', [this.getMasks(), context]);
712
1188
  }
1189
+ emitAnnotationsChanged(context) {
1190
+ this.emitOptionCallback('onAnnotationsChanged', [this.getAnnotations(), context]);
1191
+ }
713
1192
  emitBusyChangeIfChanged(context) {
714
1193
  const isBusy = this.isBusy();
715
1194
  if (this.lastEmittedIsBusy === isBusy)
@@ -718,11 +1197,20 @@ export class ImageEditor {
718
1197
  this.emitOptionCallback('onBusyChange', [isBusy, context]);
719
1198
  }
720
1199
  buildSelection(selected) {
721
- var _a;
1200
+ var _a, _b;
722
1201
  const selectedMasks = selected.filter(isMaskObject);
1202
+ const selectedAnnotations = selected.filter(isAnnotationObject);
1203
+ const selectedObjectKind = selectedMasks.length === 1 && selectedAnnotations.length === 0
1204
+ ? 'mask'
1205
+ : selectedAnnotations.length === 1 && selectedMasks.length === 0
1206
+ ? 'annotation'
1207
+ : null;
723
1208
  return {
724
1209
  selectedMask: (_a = selectedMasks[0]) !== null && _a !== void 0 ? _a : null,
725
1210
  selectedMasks,
1211
+ selectedAnnotation: (_b = selectedAnnotations[0]) !== null && _b !== void 0 ? _b : null,
1212
+ selectedAnnotations,
1213
+ selectedObjectKind,
726
1214
  };
727
1215
  }
728
1216
  withSelectionChangeContext(context, callback) {
@@ -761,7 +1249,7 @@ export class ImageEditor {
761
1249
  applyCanvasDimensions(this.canvas, widthPx, heightPx, this.containerElement);
762
1250
  }
763
1251
  alignObjectBoundingBoxToCanvasTopLeft(object) {
764
- var _a, _b;
1252
+ var _a, _b, _c;
765
1253
  object.setCoords();
766
1254
  const boundingRect = object.getBoundingRect();
767
1255
  object.set({
@@ -769,7 +1257,7 @@ export class ImageEditor {
769
1257
  top: ((_b = object.top) !== null && _b !== void 0 ? _b : 0) - boundingRect.top,
770
1258
  });
771
1259
  object.setCoords();
772
- this.canvas.renderAll();
1260
+ (_c = this.canvas) === null || _c === void 0 ? void 0 : _c.renderAll();
773
1261
  }
774
1262
  measureLayoutViewport(scrollbarSize) {
775
1263
  return this.viewportCache.measure(this.containerElement, {
@@ -777,7 +1265,13 @@ export class ImageEditor {
777
1265
  height: this.options.canvasHeight,
778
1266
  }, scrollbarSize);
779
1267
  }
780
- updateCanvasSizeToImageBounds() {
1268
+ getScrollbarStableViewportCanvasSize(viewport) {
1269
+ return {
1270
+ width: Math.max(1, viewport.width - 1),
1271
+ height: Math.max(1, viewport.height - 1),
1272
+ };
1273
+ }
1274
+ updateCanvasSizeToImageBounds(options = {}) {
781
1275
  var _a, _b;
782
1276
  if (!this.originalImage)
783
1277
  return;
@@ -785,13 +1279,26 @@ export class ImageEditor {
785
1279
  const boundingRect = this.originalImage.getBoundingRect();
786
1280
  const scrollbarSize = measureScrollbarSize((_b = (_a = this.containerElement) === null || _a === void 0 ? void 0 : _a.ownerDocument) !== null && _b !== void 0 ? _b : null);
787
1281
  const viewport = this.measureLayoutViewport(scrollbarSize);
788
- if (this.options.fitImageToCanvas || this.options.coverImageToCanvas) {
1282
+ const shouldStabilizeContainedViewport = options.stabilizeContainedViewport !== false;
1283
+ const imageFitsViewport = boundingRect.width <= viewport.width + LAYOUT_EPSILON &&
1284
+ boundingRect.height <= viewport.height + LAYOUT_EPSILON;
1285
+ if (this.currentLayoutMode === 'fit' || this.currentLayoutMode === 'cover') {
1286
+ if (imageFitsViewport) {
1287
+ const canvasSize = shouldStabilizeContainedViewport
1288
+ ? this.getScrollbarStableViewportCanvasSize(viewport)
1289
+ : viewport;
1290
+ this.setCanvasSizePx(canvasSize.width, canvasSize.height);
1291
+ return;
1292
+ }
789
1293
  const canvasSize = computeScrollableCanvasSize(boundingRect.width, boundingRect.height, viewport, scrollbarSize);
790
1294
  this.setCanvasSizePx(canvasSize.width, canvasSize.height);
791
1295
  return;
792
1296
  }
793
- if (boundingRect.width <= viewport.width && boundingRect.height <= viewport.height) {
794
- this.setCanvasSizePx(viewport.width, viewport.height);
1297
+ if (imageFitsViewport) {
1298
+ const canvasSize = shouldStabilizeContainedViewport
1299
+ ? this.getScrollbarStableViewportCanvasSize(viewport)
1300
+ : viewport;
1301
+ this.setCanvasSizePx(canvasSize.width, canvasSize.height);
795
1302
  return;
796
1303
  }
797
1304
  this.setCanvasSizePx(Math.max(viewport.width, Math.ceil(boundingRect.width)), Math.max(viewport.height, Math.ceil(boundingRect.height)));
@@ -807,14 +1314,14 @@ export class ImageEditor {
807
1314
  const canvasH = Math.ceil(this.canvas.getHeight());
808
1315
  const clipsImage = boundingRect.width > canvasW + LAYOUT_EPSILON ||
809
1316
  boundingRect.height > canvasH + LAYOUT_EPSILON;
810
- if (this.options.fitImageToCanvas || this.options.coverImageToCanvas) {
1317
+ if (this.currentLayoutMode === 'fit' || this.currentLayoutMode === 'cover') {
811
1318
  const staleOverflowWidth = canvasW > viewport.width + LAYOUT_EPSILON &&
812
1319
  boundingRect.width <= viewport.width + LAYOUT_EPSILON;
813
1320
  const staleOverflowHeight = canvasH > viewport.height + LAYOUT_EPSILON &&
814
1321
  boundingRect.height <= viewport.height + LAYOUT_EPSILON;
815
1322
  return clipsImage || staleOverflowWidth || staleOverflowHeight;
816
1323
  }
817
- if (this.options.expandCanvasToImage) {
1324
+ if (this.currentLayoutMode === 'expand') {
818
1325
  const expectedW = Math.max(viewport.width, Math.ceil(boundingRect.width));
819
1326
  const expectedH = Math.max(viewport.height, Math.ceil(boundingRect.height));
820
1327
  return (Math.abs(canvasW - expectedW) > LAYOUT_EPSILON ||
@@ -822,6 +1329,33 @@ export class ImageEditor {
822
1329
  }
823
1330
  return clipsImage;
824
1331
  }
1332
+ settleFitCoverScrollbarsAfterStateRestore() {
1333
+ if (!this.canvas ||
1334
+ !this.containerElement ||
1335
+ (this.currentLayoutMode !== 'fit' && this.currentLayoutMode !== 'cover')) {
1336
+ return;
1337
+ }
1338
+ const canvasW = Math.ceil(this.canvas.getWidth());
1339
+ const canvasH = Math.ceil(this.canvas.getHeight());
1340
+ if (canvasW <= 1 || canvasH <= 1)
1341
+ return;
1342
+ const clientW = Math.floor(this.containerElement.clientWidth || 0);
1343
+ const clientH = Math.floor(this.containerElement.clientHeight || 0);
1344
+ if (clientW <= 0 || clientH <= 0)
1345
+ return;
1346
+ const scrollW = Math.ceil(this.containerElement.scrollWidth || 0);
1347
+ const scrollH = Math.ceil(this.containerElement.scrollHeight || 0);
1348
+ const hasHorizontalScrollbar = scrollW > clientW + LAYOUT_EPSILON;
1349
+ const hasVerticalScrollbar = scrollH > clientH + LAYOUT_EPSILON;
1350
+ if (!hasHorizontalScrollbar && !hasVerticalScrollbar)
1351
+ return;
1352
+ const nudgeWidth = hasVerticalScrollbar && Math.abs(canvasW - clientW) <= SCROLLBAR_SETTLE_EPSILON;
1353
+ const nudgeHeight = hasHorizontalScrollbar && Math.abs(canvasH - clientH) <= SCROLLBAR_SETTLE_EPSILON;
1354
+ if (!nudgeWidth && !nudgeHeight)
1355
+ return;
1356
+ this.setCanvasSizePx(nudgeWidth ? canvasW - 1 : canvasW, nudgeHeight ? canvasH - 1 : canvasH);
1357
+ this.setCanvasSizePx(canvasW, canvasH);
1358
+ }
825
1359
  captureImageDisplayGeometry() {
826
1360
  if (!this.canvas || !this.originalImage)
827
1361
  return null;
@@ -864,7 +1398,7 @@ export class ImageEditor {
864
1398
  }
865
1399
  buildTransformContext() {
866
1400
  return {
867
- canvas: this.canvas,
1401
+ canvas: this.getLiveCanvasOrThrow('buildTransformContext'),
868
1402
  options: this.options,
869
1403
  guard: this.operationGuard,
870
1404
  getOriginalImage: () => this.originalImage,
@@ -886,11 +1420,7 @@ export class ImageEditor {
886
1420
  afterTransformSnap: () => {
887
1421
  if (this.isDisposed || !this.canvas || !this.originalImage)
888
1422
  return;
889
- if (this.options.expandCanvasToImage ||
890
- this.options.coverImageToCanvas ||
891
- this.options.fitImageToCanvas) {
892
- this.updateCanvasSizeToImageBounds();
893
- }
1423
+ this.updateCanvasSizeToImageBounds();
894
1424
  this.alignObjectBoundingBoxToCanvasTopLeft(this.originalImage);
895
1425
  this.canvas
896
1426
  .getObjects()
@@ -1020,6 +1550,7 @@ export class ImageEditor {
1020
1550
  const context = this.buildCallbackContext(activeRestoreOperation !== null && activeRestoreOperation !== void 0 ? activeRestoreOperation : 'loadFromState', activeRestoreOperation === 'undo' || activeRestoreOperation === 'redo');
1021
1551
  const previousImage = this.originalImage;
1022
1552
  const previousMaskSignature = this.getMaskCollectionSignature();
1553
+ const previousAnnotationSignature = this.getAnnotationCollectionSignature();
1023
1554
  try {
1024
1555
  const restoredState = await loadFromStateImpl({
1025
1556
  canvas: this.canvas,
@@ -1042,6 +1573,7 @@ export class ImageEditor {
1042
1573
  this.canvas.sendObjectToBack(this.originalImage);
1043
1574
  }
1044
1575
  this.maskCounter = restoredState.maxMaskId;
1576
+ this.annotationCounter = restoredState.maxAnnotationId;
1045
1577
  const editorState = restoredState.editorState;
1046
1578
  if (editorState) {
1047
1579
  this.currentScale = editorState.currentScale;
@@ -1058,24 +1590,26 @@ export class ImageEditor {
1058
1590
  this.currentImageMimeType = null;
1059
1591
  }
1060
1592
  this.isImageLoadedToCanvas = !!this.originalImage;
1061
- if (this.originalImage &&
1062
- (this.options.expandCanvasToImage ||
1063
- this.options.coverImageToCanvas ||
1064
- this.options.fitImageToCanvas) &&
1065
- this.shouldNormalizeCanvasSizeAfterStateRestore()) {
1066
- this.updateCanvasSizeToImageBounds();
1593
+ if (this.originalImage && this.shouldNormalizeCanvasSizeAfterStateRestore()) {
1594
+ this.updateCanvasSizeToImageBounds({ stabilizeContainedViewport: false });
1067
1595
  this.alignObjectBoundingBoxToCanvasTopLeft(this.originalImage);
1068
1596
  }
1069
- const restoredMasks = restoredState.objects.filter(isMaskObject);
1597
+ if (this.originalImage) {
1598
+ this.settleFitCoverScrollbarsAfterStateRestore();
1599
+ }
1600
+ const restoredMasks = restoredState.masks;
1070
1601
  this.lastMask = restoredMasks.reduce((lastMask, maskObject) => !lastMask || maskObject.maskId > lastMask.maskId ? maskObject : lastMask, null);
1071
1602
  restoredMasks.forEach((maskObject) => {
1072
1603
  applyMaskUnselectedStyle(maskObject);
1073
1604
  reattachMaskHoverHandlers(maskObject);
1074
1605
  });
1606
+ syncAnnotationRuntimeStates(restoredState.annotations);
1607
+ attachTextEditingHandlersToAnnotations(this.buildTextControllerContext(), restoredState.annotations);
1075
1608
  this.lastSnapshot = this.captureSnapshotInternal();
1076
1609
  this.canvas.renderAll();
1077
1610
  this.updateInputs();
1078
1611
  this.updateMaskList();
1612
+ this.updateAnnotationList();
1079
1613
  this.updateUi();
1080
1614
  if (previousImage && previousImage !== this.originalImage) {
1081
1615
  this.emitOptionCallback('onImageCleared', [previousImage, context]);
@@ -1083,17 +1617,32 @@ export class ImageEditor {
1083
1617
  if (previousMaskSignature !== this.getMaskCollectionSignature()) {
1084
1618
  this.emitMasksChanged(context);
1085
1619
  }
1620
+ if (previousAnnotationSignature !== this.getAnnotationCollectionSignature()) {
1621
+ this.emitAnnotationsChanged(context);
1622
+ }
1086
1623
  this.emitImageChanged(context);
1624
+ const canvas = this.getLiveCanvasOrThrow('loadFromState');
1087
1625
  const activeMaskId = editorState === null || editorState === void 0 ? void 0 : editorState.activeMaskId;
1088
- if (typeof activeMaskId === 'number') {
1626
+ const activeAnnotationId = editorState === null || editorState === void 0 ? void 0 : editorState.activeAnnotationId;
1627
+ if ((editorState === null || editorState === void 0 ? void 0 : editorState.activeObjectKind) === 'mask' && typeof activeMaskId === 'number') {
1089
1628
  const activeMask = restoredMasks.find((maskObject) => maskObject.maskId === activeMaskId);
1090
1629
  if (activeMask) {
1091
1630
  this.withSelectionChangeContext(context, () => {
1092
- this.canvas.setActiveObject(activeMask);
1631
+ canvas.setActiveObject(activeMask);
1093
1632
  this.handleSelectionChanged([activeMask]);
1094
1633
  });
1095
1634
  }
1096
1635
  }
1636
+ else if ((editorState === null || editorState === void 0 ? void 0 : editorState.activeObjectKind) === 'annotation' &&
1637
+ typeof activeAnnotationId === 'number') {
1638
+ const activeAnnotation = restoredState.annotations.find((annotation) => annotation.annotationId === activeAnnotationId);
1639
+ if (activeAnnotation) {
1640
+ this.withSelectionChangeContext(context, () => {
1641
+ canvas.setActiveObject(activeAnnotation);
1642
+ this.handleSelectionChanged([activeAnnotation]);
1643
+ });
1644
+ }
1645
+ }
1097
1646
  }
1098
1647
  catch (error) {
1099
1648
  reportError(this.options, error, 'Failed to restore canvas state.');
@@ -1104,62 +1653,67 @@ export class ImageEditor {
1104
1653
  this.saveStateInternal();
1105
1654
  }
1106
1655
  saveStateInternal(options) {
1107
- var _a, _b;
1656
+ var _a, _b, _c;
1108
1657
  if (!this.canvas || this.shouldSuppressSaveState)
1109
1658
  return;
1110
1659
  if (!this.canRunIdleOperation('saveState', options))
1111
1660
  return;
1112
1661
  const activeObj = this.canvas.getActiveObject();
1113
1662
  const activeMask = this.getActiveMaskForSnapshot();
1663
+ const activeAnnotation = this.getActiveAnnotationForSnapshot();
1114
1664
  this.hideAllMaskLabels();
1115
1665
  try {
1116
1666
  const after = saveStateImpl({
1117
1667
  canvas: this.canvas,
1118
1668
  activeMaskId: (_a = activeMask === null || activeMask === void 0 ? void 0 : activeMask.maskId) !== null && _a !== void 0 ? _a : null,
1669
+ activeAnnotationId: (_b = activeAnnotation === null || activeAnnotation === void 0 ? void 0 : activeAnnotation.annotationId) !== null && _b !== void 0 ? _b : null,
1119
1670
  currentScale: this.currentScale,
1120
1671
  currentRotation: this.currentRotation,
1121
1672
  baseImageScale: this.baseImageScale,
1122
1673
  currentImageMimeType: this.currentImageMimeType,
1123
1674
  });
1124
- const before = (_b = this.lastSnapshot) !== null && _b !== void 0 ? _b : after;
1675
+ const before = (_c = this.lastSnapshot) !== null && _c !== void 0 ? _c : after;
1125
1676
  if (after === before) {
1126
1677
  return;
1127
1678
  }
1128
- let executedOnce = false;
1129
1679
  const cmd = new Command(async () => {
1130
- if (executedOnce) {
1131
- await this.loadFromStateInternal(after, this.withAnimationQueueBypass());
1132
- }
1133
- executedOnce = true;
1680
+ await this.loadFromStateInternal(after, this.withAnimationQueueBypass());
1134
1681
  }, async () => {
1135
1682
  await this.loadFromStateInternal(before, this.withAnimationQueueBypass());
1136
1683
  });
1137
- this.historyManager.execute(cmd);
1684
+ this.historyManager.push(cmd);
1138
1685
  this.lastSnapshot = after;
1139
1686
  }
1140
1687
  catch (error) {
1141
1688
  reportWarning(this.options, error, 'Failed to capture canvas snapshot.');
1142
1689
  }
1143
1690
  finally {
1144
- this.restoreActiveMaskAfterSnapshot(activeObj, activeMask);
1691
+ this.restoreActiveObjectAfterSnapshot(activeObj, activeMask, activeAnnotation);
1145
1692
  this.updateUi();
1146
1693
  }
1147
1694
  }
1148
- restoreActiveMaskAfterSnapshot(activeObj, activeMask) {
1695
+ restoreActiveObjectAfterSnapshot(activeObj, activeMask, activeAnnotation) {
1149
1696
  if (!this.canvas)
1150
1697
  return;
1151
1698
  const maskToRestore = activeObj && isMaskObject(activeObj) ? activeObj : activeMask;
1152
- if (!maskToRestore || !this.canvas.getObjects().includes(maskToRestore))
1699
+ const annotationToRestore = activeObj && isAnnotationObject(activeObj) ? activeObj : activeAnnotation;
1700
+ if (maskToRestore && this.canvas.getObjects().includes(maskToRestore)) {
1701
+ this.canvas.setActiveObject(maskToRestore);
1702
+ this.showLabelForMask(maskToRestore);
1703
+ this.updateMaskListSelection(maskToRestore);
1153
1704
  return;
1154
- this.canvas.setActiveObject(maskToRestore);
1155
- this.showLabelForMask(maskToRestore);
1156
- this.updateMaskListSelection(maskToRestore);
1705
+ }
1706
+ if (annotationToRestore && this.canvas.getObjects().includes(annotationToRestore)) {
1707
+ this.canvas.setActiveObject(annotationToRestore);
1708
+ this.updateAnnotationListSelection(annotationToRestore);
1709
+ }
1157
1710
  }
1158
1711
  undo() {
1159
1712
  if (this.isDisposed)
1160
1713
  return Promise.resolve();
1161
1714
  if (!this.canRunIdleOperation('undo'))
1162
1715
  return Promise.resolve();
1716
+ this.finalizeActiveTextEditingIfNeeded();
1163
1717
  const context = this.buildCallbackContext('undo', true);
1164
1718
  const job = this.animQueue.add(async () => {
1165
1719
  if (this.isDisposed)
@@ -1183,6 +1737,7 @@ export class ImageEditor {
1183
1737
  return Promise.resolve();
1184
1738
  if (!this.canRunIdleOperation('redo'))
1185
1739
  return Promise.resolve();
1740
+ this.finalizeActiveTextEditingIfNeeded();
1186
1741
  const context = this.buildCallbackContext('redo', true);
1187
1742
  const job = this.animQueue.add(async () => {
1188
1743
  if (this.isDisposed)
@@ -1248,8 +1803,8 @@ export class ImageEditor {
1248
1803
  buildCreateMaskContext() {
1249
1804
  return {
1250
1805
  fabric: this.fabricModule,
1251
- canvas: this.canvas,
1252
- options: this.options,
1806
+ canvas: this.getLiveCanvasOrThrow('createMask'),
1807
+ options: this.getRuntimeOptions(),
1253
1808
  getLastMask: () => this.lastMask,
1254
1809
  setLastMask: (maskObject) => {
1255
1810
  this.lastMask = maskObject;
@@ -1271,7 +1826,7 @@ export class ImageEditor {
1271
1826
  }
1272
1827
  buildRemoveMaskContext() {
1273
1828
  return {
1274
- canvas: this.canvas,
1829
+ canvas: this.getLiveCanvasOrThrow('removeMask'),
1275
1830
  removeLabelForMask: (mask) => {
1276
1831
  this.removeLabelForMask(mask);
1277
1832
  },
@@ -1321,11 +1876,35 @@ export class ImageEditor {
1321
1876
  return;
1322
1877
  showLabelForMask(context, mask);
1323
1878
  }
1879
+ handleObjectMovingScalingRotating(target) {
1880
+ if (isMaskObject(target)) {
1881
+ this.syncMaskLabel(target);
1882
+ }
1883
+ }
1884
+ handleObjectModified(target) {
1885
+ if (isMaskObject(target)) {
1886
+ this.syncMaskLabel(target);
1887
+ const context = this.buildCallbackContext('saveState', false);
1888
+ this.saveState();
1889
+ this.emitMasksChanged(context);
1890
+ this.emitImageChanged(context);
1891
+ return;
1892
+ }
1893
+ if (isAnnotationObject(target)) {
1894
+ if (isAnnotationLocked(target))
1895
+ return;
1896
+ const context = this.buildCallbackContext('updateAnnotation', false);
1897
+ this.saveState();
1898
+ this.emitAnnotationsChanged(context);
1899
+ this.emitImageChanged(context);
1900
+ }
1901
+ }
1324
1902
  handleSelectionChanged(selected) {
1325
- var _a, _b, _c;
1903
+ var _a, _b, _c, _d;
1326
1904
  if (!this.canvas)
1327
1905
  return;
1328
1906
  const selectedMask = (_a = selected.find(isMaskObject)) !== null && _a !== void 0 ? _a : null;
1907
+ const selectedAnnotation = (_b = selected.find(isAnnotationObject)) !== null && _b !== void 0 ? _b : null;
1329
1908
  const masks = this.canvas.getObjects().filter(isMaskObject);
1330
1909
  masks.forEach((maskObject) => {
1331
1910
  if (maskObject !== selectedMask) {
@@ -1341,9 +1920,10 @@ export class ImageEditor {
1341
1920
  if (selectedMask)
1342
1921
  this.showLabelForMask(selectedMask);
1343
1922
  this.updateMaskListSelection(selectedMask);
1923
+ this.updateAnnotationListSelection(selectedAnnotation);
1344
1924
  this.canvas.requestRenderAll();
1345
1925
  this.updateUi();
1346
- const context = (_b = this.nextSelectionChangeContext) !== null && _b !== void 0 ? _b : this.buildCallbackContext((_c = this.activeStateRestoreOperation) !== null && _c !== void 0 ? _c : 'createMask', this.activeStateRestoreOperation === 'undo' ||
1926
+ const context = (_c = this.nextSelectionChangeContext) !== null && _c !== void 0 ? _c : this.buildCallbackContext((_d = this.activeStateRestoreOperation) !== null && _d !== void 0 ? _d : 'createMask', this.activeStateRestoreOperation === 'undo' ||
1347
1927
  this.activeStateRestoreOperation === 'redo');
1348
1928
  this.emitOptionCallback('onSelectionChange', [this.buildSelection(selected), context]);
1349
1929
  }
@@ -1360,68 +1940,490 @@ export class ImageEditor {
1360
1940
  updateMaskListSelection(selectedMask) {
1361
1941
  updateMaskListSelection(this.buildMaskListContext(), selectedMask);
1362
1942
  }
1363
- async mergeMasks() {
1943
+ enterTextMode() {
1364
1944
  if (!this.canvas)
1365
1945
  return;
1366
- if (!this.canRunIdleOperation('mergeMasks'))
1946
+ if (!this.canRunIdleOperation('enterTextMode'))
1367
1947
  return;
1368
- const hasMasks = this.canvas.getObjects().some(isMaskObject);
1369
- if (!hasMasks)
1948
+ if (this.isToolModeActive())
1370
1949
  return;
1371
- const callbackContext = this.buildCallbackContext('mergeMasks', false);
1372
- const operationToken = this.operationGuard.beginBusyOperation('mergeMasks');
1950
+ enterTextModeImpl(this.buildTextControllerContext());
1951
+ const callbackContext = this.buildCallbackContext('enterTextMode', false);
1373
1952
  this.emitBusyChangeIfChanged(callbackContext);
1374
- this.updateUi();
1375
- try {
1376
- const mergeMasksContext = this.buildMergeMasksContext(operationToken);
1377
- await mergeMasksImpl(mergeMasksContext);
1378
- this.updateInputs();
1379
- this.updateMaskList();
1380
- this.emitMasksChanged(callbackContext);
1381
- this.emitImageChanged(callbackContext);
1382
- }
1383
- finally {
1384
- this.operationGuard.endBusyOperation(operationToken);
1385
- this.emitBusyChangeIfChanged(callbackContext);
1386
- this.updateUi();
1387
- }
1953
+ this.emitImageChanged(callbackContext);
1388
1954
  }
1389
- downloadImage(fileName) {
1390
- if (!this.canvas)
1955
+ exitTextMode() {
1956
+ if (!this.canvas || !this.textSession)
1391
1957
  return;
1392
- if (!this.canRunIdleOperation('downloadImage'))
1958
+ if (!this.canRunIdleOperation('exitTextMode'))
1393
1959
  return;
1394
- const callbackContext = this.buildCallbackContext('downloadImage', false);
1395
- const operationToken = this.operationGuard.beginBusyOperation('downloadImage');
1960
+ exitTextModeImpl(this.buildTextControllerContext());
1961
+ const callbackContext = this.buildCallbackContext('exitTextMode', false);
1396
1962
  this.emitBusyChangeIfChanged(callbackContext);
1397
- const exportContext = this.buildExportServiceContext();
1398
- try {
1399
- downloadImageImpl(exportContext, fileName);
1400
- }
1401
- finally {
1402
- this.operationGuard.endBusyOperation(operationToken);
1403
- this.emitBusyChangeIfChanged(callbackContext);
1404
- }
1963
+ this.emitImageChanged(callbackContext);
1405
1964
  }
1406
- async exportImageBase64(options) {
1965
+ isTextMode() {
1966
+ return this.textSession !== null;
1967
+ }
1968
+ createTextAnnotation(config = {}) {
1407
1969
  if (!this.canvas)
1408
- return '';
1409
- if (!this.canRunIdleOperation('exportImageBase64', options))
1410
- return '';
1411
- const callbackContext = this.buildCallbackContext('exportImageBase64', false);
1412
- const operationToken = this.operationGuard.beginBusyOperation('exportImageBase64');
1413
- this.emitBusyChangeIfChanged(callbackContext);
1414
- const exportContext = this.buildExportServiceContext();
1415
- try {
1416
- return await exportImageBase64Impl(exportContext, options);
1417
- }
1418
- finally {
1419
- this.operationGuard.endBusyOperation(operationToken);
1420
- this.emitBusyChangeIfChanged(callbackContext);
1421
- }
1970
+ return null;
1971
+ if (!this.canRunIdleOperation('createTextAnnotation'))
1972
+ return null;
1973
+ return createTextAnnotationImpl(this.buildTextControllerContext(), config);
1422
1974
  }
1423
- async exportImageFile(options) {
1424
- this.assertIdleForOperation('exportImageFile', options);
1975
+ enterDrawMode() {
1976
+ if (!this.canvas)
1977
+ return;
1978
+ if (!this.canRunIdleOperation('enterDrawMode'))
1979
+ return;
1980
+ if (this.isToolModeActive())
1981
+ return;
1982
+ enterDrawModeImpl(this.buildDrawControllerContext());
1983
+ const callbackContext = this.buildCallbackContext('enterDrawMode', false);
1984
+ this.emitBusyChangeIfChanged(callbackContext);
1985
+ this.emitImageChanged(callbackContext);
1986
+ }
1987
+ exitDrawMode() {
1988
+ if (!this.canvas || !this.drawSession)
1989
+ return;
1990
+ if (!this.canRunIdleOperation('exitDrawMode'))
1991
+ return;
1992
+ exitDrawModeImpl(this.buildDrawControllerContext());
1993
+ const callbackContext = this.buildCallbackContext('exitDrawMode', false);
1994
+ this.emitBusyChangeIfChanged(callbackContext);
1995
+ this.emitImageChanged(callbackContext);
1996
+ }
1997
+ isDrawMode() {
1998
+ return this.drawSession !== null;
1999
+ }
2000
+ getTextConfig() {
2001
+ return cloneResolvedTextAnnotationConfig(this.currentTextConfig);
2002
+ }
2003
+ setTextConfig(config) {
2004
+ this.applyTextConfigPatch(config, 'setTextConfig');
2005
+ }
2006
+ resetTextConfig() {
2007
+ this.applyTextConfigPatch(this.defaultTextConfig, 'resetTextConfig');
2008
+ }
2009
+ setTextColor(color) {
2010
+ this.applyTextConfigPatch({ fill: color }, 'setTextColor');
2011
+ }
2012
+ setTextFontSize(size) {
2013
+ this.applyTextConfigPatch({ fontSize: size }, 'setTextFontSize');
2014
+ }
2015
+ getDrawConfig() {
2016
+ return cloneResolvedDrawConfig(this.currentDrawConfig);
2017
+ }
2018
+ setDrawConfig(config) {
2019
+ this.applyDrawConfigPatch(config, 'setDrawConfig');
2020
+ }
2021
+ resetDrawConfig() {
2022
+ this.applyDrawConfigPatch(this.defaultDrawConfig, 'resetDrawConfig');
2023
+ }
2024
+ setDrawColor(color) {
2025
+ this.applyDrawConfigPatch({ color }, 'setDrawColor');
2026
+ }
2027
+ setDrawBrushSize(size) {
2028
+ this.applyDrawConfigPatch({ brushSize: size }, 'setDrawBrushSize');
2029
+ }
2030
+ removeSelectedAnnotation() {
2031
+ if (!this.canvas)
2032
+ return;
2033
+ if (!this.canRunIdleOperation('removeSelectedAnnotation'))
2034
+ return;
2035
+ const before = this.getAnnotations().length;
2036
+ const callbackContext = this.buildCallbackContext('removeSelectedAnnotation', false);
2037
+ this.withSelectionChangeContext(callbackContext, () => {
2038
+ removeSelectedAnnotationImpl(this.buildAnnotationManagerContext());
2039
+ });
2040
+ this.updateAnnotationList();
2041
+ this.updateUi();
2042
+ if (this.getAnnotations().length !== before) {
2043
+ this.emitAnnotationsChanged(callbackContext);
2044
+ this.emitImageChanged(callbackContext);
2045
+ }
2046
+ }
2047
+ removeAllAnnotations(options = {}) {
2048
+ if (!this.canvas)
2049
+ return;
2050
+ if (!this.canRunIdleOperation('removeAllAnnotations', options))
2051
+ return;
2052
+ const before = this.getAnnotations().length;
2053
+ const callbackContext = this.buildCallbackContext('removeAllAnnotations', false);
2054
+ this.withSelectionChangeContext(callbackContext, () => {
2055
+ removeAllAnnotationsImpl(this.buildAnnotationManagerContext(), options);
2056
+ });
2057
+ this.updateAnnotationList();
2058
+ this.updateUi();
2059
+ if (this.getAnnotations().length !== before) {
2060
+ this.emitAnnotationsChanged(callbackContext);
2061
+ this.emitImageChanged(callbackContext);
2062
+ }
2063
+ }
2064
+ updateAnnotation(annotationId, config) {
2065
+ if (!this.canvas)
2066
+ return;
2067
+ if (!this.canRunIdleOperation('updateAnnotation'))
2068
+ return;
2069
+ const callbackContext = this.buildCallbackContext('updateAnnotation', false);
2070
+ const changed = updateAnnotationImpl(this.buildAnnotationManagerContext(), annotationId, config);
2071
+ if (changed) {
2072
+ this.updateAnnotationList();
2073
+ this.emitAnnotationsChanged(callbackContext);
2074
+ this.emitImageChanged(callbackContext);
2075
+ }
2076
+ }
2077
+ updateSelectedAnnotation(config) {
2078
+ if (!this.canvas)
2079
+ return;
2080
+ if (!this.canRunIdleOperation('updateSelectedAnnotation'))
2081
+ return;
2082
+ const callbackContext = this.buildCallbackContext('updateSelectedAnnotation', false);
2083
+ const changed = updateSelectedAnnotationImpl(this.buildAnnotationManagerContext(), config);
2084
+ if (changed) {
2085
+ this.updateAnnotationList();
2086
+ this.emitAnnotationsChanged(callbackContext);
2087
+ this.emitImageChanged(callbackContext);
2088
+ }
2089
+ }
2090
+ deleteSelectedObject() {
2091
+ if (!this.canvas)
2092
+ return;
2093
+ if (!this.canRunIdleOperation('deleteSelectedObject'))
2094
+ return;
2095
+ this.finalizeActiveTextEditingIfNeeded();
2096
+ const selectedObjects = this.getSelectedCanvasObjects();
2097
+ const selectedMasks = selectedObjects.filter(isMaskObject);
2098
+ const selectedAnnotations = selectedObjects.filter((object) => isAnnotationObject(object) && isAnnotationUnlocked(object));
2099
+ if (selectedMasks.length === 0 && selectedAnnotations.length === 0)
2100
+ return;
2101
+ const canvas = this.getLiveCanvasOrThrow('deleteSelectedObject');
2102
+ const callbackContext = this.buildCallbackContext('deleteSelectedObject', false);
2103
+ this.withSelectionChangeContext(callbackContext, () => {
2104
+ for (const mask of selectedMasks) {
2105
+ this.removeLabelForMask(mask);
2106
+ canvas.remove(mask);
2107
+ }
2108
+ removeAnnotationObjects(this.buildAnnotationManagerContext(), selectedAnnotations, {
2109
+ saveHistory: false,
2110
+ force: true,
2111
+ });
2112
+ canvas.discardActiveObject();
2113
+ canvas.renderAll();
2114
+ this.saveState();
2115
+ });
2116
+ this.updateMaskList();
2117
+ this.updateAnnotationList();
2118
+ this.updateUi();
2119
+ if (selectedMasks.length > 0)
2120
+ this.emitMasksChanged(callbackContext);
2121
+ if (selectedAnnotations.length > 0)
2122
+ this.emitAnnotationsChanged(callbackContext);
2123
+ this.emitImageChanged(callbackContext);
2124
+ }
2125
+ bringSelectedObjectForward() {
2126
+ this.moveSelectedEditableObject('bringSelectedObjectForward');
2127
+ }
2128
+ sendSelectedObjectBackward() {
2129
+ this.moveSelectedEditableObject('sendSelectedObjectBackward');
2130
+ }
2131
+ bringSelectedObjectToFront() {
2132
+ this.moveSelectedEditableObject('bringSelectedObjectToFront');
2133
+ }
2134
+ sendSelectedObjectToBack() {
2135
+ this.moveSelectedEditableObject('sendSelectedObjectToBack');
2136
+ }
2137
+ buildAnnotationManagerContext() {
2138
+ return {
2139
+ canvas: this.getLiveCanvasOrThrow('annotationManager'),
2140
+ saveCanvasState: () => this.saveState(),
2141
+ updateUi: () => this.updateUi(),
2142
+ };
2143
+ }
2144
+ buildAnnotationListContext() {
2145
+ return {
2146
+ canvas: this.canvas,
2147
+ getListElementId: () => this.elements.annotationList,
2148
+ onAnnotationSelected: (annotation) => this.handleSelectionChanged([annotation]),
2149
+ };
2150
+ }
2151
+ updateAnnotationList() {
2152
+ renderAnnotationList(this.buildAnnotationListContext());
2153
+ }
2154
+ updateAnnotationListSelection(selectedAnnotation) {
2155
+ updateAnnotationListSelection(this.buildAnnotationListContext(), selectedAnnotation);
2156
+ }
2157
+ buildTextControllerContext() {
2158
+ return {
2159
+ fabric: this.fabricModule,
2160
+ canvas: this.getLiveCanvasOrThrow('textController'),
2161
+ options: this.options,
2162
+ getOriginalImage: () => this.originalImage,
2163
+ getTextConfig: () => this.currentTextConfig,
2164
+ isImageLoaded: () => this.isImageLoaded(),
2165
+ getAnnotationCounter: () => this.annotationCounter,
2166
+ setAnnotationCounter: (value) => {
2167
+ this.annotationCounter = value;
2168
+ },
2169
+ getTextSession: () => this.textSession,
2170
+ setTextSession: (session) => {
2171
+ this.textSession = session;
2172
+ },
2173
+ saveCanvasState: () => this.saveState(),
2174
+ updateAnnotationList: () => this.updateAnnotationList(),
2175
+ updateUi: () => this.updateUi(),
2176
+ emitAnnotationsChanged: (context) => this.emitAnnotationsChanged(context),
2177
+ emitImageChanged: (context) => this.emitImageChanged(context),
2178
+ buildCallbackContext: (operation) => this.buildCallbackContext(operation, false),
2179
+ };
2180
+ }
2181
+ buildDrawControllerContext() {
2182
+ return {
2183
+ fabric: this.fabricModule,
2184
+ canvas: this.getLiveCanvasOrThrow('drawController'),
2185
+ options: this.options,
2186
+ getDrawConfig: () => this.currentDrawConfig,
2187
+ isImageLoaded: () => this.isImageLoaded(),
2188
+ getAnnotationCounter: () => this.annotationCounter,
2189
+ setAnnotationCounter: (value) => {
2190
+ this.annotationCounter = value;
2191
+ },
2192
+ getDrawSession: () => this.drawSession,
2193
+ setDrawSession: (session) => {
2194
+ this.drawSession = session;
2195
+ },
2196
+ saveCanvasState: () => this.saveState(),
2197
+ updateAnnotationList: () => this.updateAnnotationList(),
2198
+ updateUi: () => this.updateUi(),
2199
+ emitAnnotationsChanged: (context) => this.emitAnnotationsChanged(context),
2200
+ emitImageChanged: (context) => this.emitImageChanged(context),
2201
+ buildCallbackContext: (operation) => this.buildCallbackContext(operation, false),
2202
+ };
2203
+ }
2204
+ applyTextConfigPatch(config, operation) {
2205
+ if (!this.canRunIdleOperation(operation))
2206
+ return;
2207
+ const invalidFields = getInvalidTextAnnotationConfigFields(config);
2208
+ if (invalidFields.length > 0) {
2209
+ reportWarning(this.options, null, `${operation} ignored invalid Text config fields: ${invalidFields.join(', ')}.`);
2210
+ }
2211
+ const next = mergeTextAnnotationConfigPatch(this.currentTextConfig, config, this.defaultTextConfig);
2212
+ if (areResolvedTextAnnotationConfigsEqual(this.currentTextConfig, next))
2213
+ return;
2214
+ this.currentTextConfig = next;
2215
+ this.updateInputs();
2216
+ this.updateUi();
2217
+ this.emitImageChanged(this.buildCallbackContext(operation, false));
2218
+ }
2219
+ applyDrawConfigPatch(config, operation) {
2220
+ if (!this.canRunIdleOperation(operation))
2221
+ return;
2222
+ const invalidFields = getInvalidDrawConfigFields(config);
2223
+ if (invalidFields.length > 0) {
2224
+ reportWarning(this.options, null, `${operation} ignored invalid Draw config fields: ${invalidFields.join(', ')}.`);
2225
+ }
2226
+ const next = mergeDrawConfigPatch(this.currentDrawConfig, config, this.defaultDrawConfig);
2227
+ if (areResolvedDrawConfigsEqual(this.currentDrawConfig, next))
2228
+ return;
2229
+ this.currentDrawConfig = next;
2230
+ updateDrawBrush(this.buildDrawControllerContext());
2231
+ this.updateInputs();
2232
+ this.updateUi();
2233
+ this.emitImageChanged(this.buildCallbackContext(operation, false));
2234
+ }
2235
+ applyTextColorInput(color) {
2236
+ var _a;
2237
+ if (this.isTextMode()) {
2238
+ this.setTextColor(color);
2239
+ return;
2240
+ }
2241
+ const selected = (_a = this.canvas) === null || _a === void 0 ? void 0 : _a.getActiveObject();
2242
+ if (selected && isTextAnnotationObject(selected)) {
2243
+ this.updateSelectedAnnotation({ fill: color });
2244
+ return;
2245
+ }
2246
+ this.setTextColor(color);
2247
+ }
2248
+ applyTextFontSizeInput(size) {
2249
+ var _a;
2250
+ if (this.isTextMode()) {
2251
+ this.setTextFontSize(size);
2252
+ return;
2253
+ }
2254
+ const selected = (_a = this.canvas) === null || _a === void 0 ? void 0 : _a.getActiveObject();
2255
+ if (selected && isTextAnnotationObject(selected)) {
2256
+ this.updateSelectedAnnotation({ fontSize: size });
2257
+ return;
2258
+ }
2259
+ this.setTextFontSize(size);
2260
+ }
2261
+ applyDrawColorInput(color) {
2262
+ var _a;
2263
+ if (this.isDrawMode()) {
2264
+ this.setDrawColor(color);
2265
+ return;
2266
+ }
2267
+ const selected = (_a = this.canvas) === null || _a === void 0 ? void 0 : _a.getActiveObject();
2268
+ if (selected && isDrawAnnotationObject(selected)) {
2269
+ this.updateSelectedAnnotation({ stroke: color });
2270
+ return;
2271
+ }
2272
+ this.setDrawColor(color);
2273
+ }
2274
+ applyDrawBrushSizeInput(size) {
2275
+ var _a;
2276
+ if (this.isDrawMode()) {
2277
+ this.setDrawBrushSize(size);
2278
+ return;
2279
+ }
2280
+ const selected = (_a = this.canvas) === null || _a === void 0 ? void 0 : _a.getActiveObject();
2281
+ if (selected && isDrawAnnotationObject(selected)) {
2282
+ this.updateSelectedAnnotation({ strokeWidth: size });
2283
+ return;
2284
+ }
2285
+ this.setDrawBrushSize(size);
2286
+ }
2287
+ getSelectedCanvasObjects() {
2288
+ var _a, _b, _c;
2289
+ if (!this.canvas)
2290
+ return [];
2291
+ const activeObject = this.canvas.getActiveObject();
2292
+ if (!activeObject)
2293
+ return [];
2294
+ const type = typeof activeObject.type === 'string' ? activeObject.type.toLowerCase() : '';
2295
+ const isActiveSelection = type === 'activeselection' ||
2296
+ ((_c = (_b = (_a = activeObject).isType) === null || _b === void 0 ? void 0 : _b.call(_a, 'ActiveSelection')) !== null && _c !== void 0 ? _c : false);
2297
+ if (!isActiveSelection)
2298
+ return [activeObject];
2299
+ const getObjects = activeObject
2300
+ .getObjects;
2301
+ return typeof getObjects === 'function' ? getObjects.call(activeObject) : [];
2302
+ }
2303
+ moveSelectedEditableObject(operation) {
2304
+ if (!this.canvas)
2305
+ return;
2306
+ if (!this.canRunIdleOperation(operation))
2307
+ return;
2308
+ const selected = this.getSelectedCanvasObjects().filter(isEditableOverlayObject);
2309
+ if (selected.length !== 1) {
2310
+ if (selected.length > 1) {
2311
+ reportWarning(this.options, null, `${operation} skipped: ActiveSelection layer moves are not supported.`);
2312
+ }
2313
+ return;
2314
+ }
2315
+ const object = selected[0];
2316
+ const range = getEditableOverlayRange(this.canvas);
2317
+ const overlays = range.overlays;
2318
+ const currentOverlayIndex = overlays.indexOf(object);
2319
+ if (currentOverlayIndex < 0)
2320
+ return;
2321
+ let nextOverlayIndex = currentOverlayIndex;
2322
+ if (operation === 'bringSelectedObjectForward') {
2323
+ nextOverlayIndex = Math.min(overlays.length - 1, currentOverlayIndex + 1);
2324
+ }
2325
+ else if (operation === 'sendSelectedObjectBackward') {
2326
+ nextOverlayIndex = Math.max(0, currentOverlayIndex - 1);
2327
+ }
2328
+ else if (operation === 'bringSelectedObjectToFront') {
2329
+ nextOverlayIndex = overlays.length - 1;
2330
+ }
2331
+ else if (operation === 'sendSelectedObjectToBack') {
2332
+ nextOverlayIndex = 0;
2333
+ }
2334
+ if (nextOverlayIndex === currentOverlayIndex)
2335
+ return;
2336
+ const reordered = overlays.slice();
2337
+ reordered.splice(currentOverlayIndex, 1);
2338
+ reordered.splice(nextOverlayIndex, 0, object);
2339
+ reordered.forEach((overlay, index) => {
2340
+ var _a, _b;
2341
+ (_b = (_a = this.canvas).moveObjectTo) === null || _b === void 0 ? void 0 : _b.call(_a, overlay, range.start + index);
2342
+ });
2343
+ normalizeLayerOrder(this.canvas);
2344
+ this.canvas.setActiveObject(object);
2345
+ this.canvas.renderAll();
2346
+ this.saveState();
2347
+ this.updateMaskList();
2348
+ this.updateAnnotationList();
2349
+ this.updateUi();
2350
+ const context = this.buildCallbackContext(operation, false);
2351
+ if (isMaskObject(object))
2352
+ this.emitMasksChanged(context);
2353
+ if (isAnnotationObject(object))
2354
+ this.emitAnnotationsChanged(context);
2355
+ this.emitImageChanged(context);
2356
+ }
2357
+ async mergeMasks() {
2358
+ if (!this.canvas)
2359
+ return;
2360
+ if (!this.canRunIdleOperation('mergeMasks'))
2361
+ return;
2362
+ this.finalizeActiveTextEditingIfNeeded();
2363
+ const hasMasks = this.canvas.getObjects().some(isMaskObject);
2364
+ if (!hasMasks)
2365
+ return;
2366
+ const callbackContext = this.buildCallbackContext('mergeMasks', false);
2367
+ const operationToken = this.operationGuard.beginBusyOperation('mergeMasks');
2368
+ this.emitBusyChangeIfChanged(callbackContext);
2369
+ this.updateUi();
2370
+ try {
2371
+ const mergeMasksContext = this.buildMergeMasksContext(operationToken);
2372
+ await mergeMasksImpl(mergeMasksContext);
2373
+ this.updateInputs();
2374
+ this.updateMaskList();
2375
+ this.updateAnnotationList();
2376
+ this.emitMasksChanged(callbackContext);
2377
+ if (this.getAnnotations().length > 0) {
2378
+ this.emitAnnotationsChanged(callbackContext);
2379
+ }
2380
+ this.emitImageChanged(callbackContext);
2381
+ }
2382
+ finally {
2383
+ this.operationGuard.endBusyOperation(operationToken);
2384
+ this.emitBusyChangeIfChanged(callbackContext);
2385
+ this.updateUi();
2386
+ }
2387
+ }
2388
+ downloadImage(options) {
2389
+ if (!this.canvas)
2390
+ return;
2391
+ if (!this.canRunIdleOperation('downloadImage'))
2392
+ return;
2393
+ this.finalizeActiveTextEditingIfNeeded();
2394
+ const callbackContext = this.buildCallbackContext('downloadImage', false);
2395
+ const operationToken = this.operationGuard.beginBusyOperation('downloadImage');
2396
+ this.emitBusyChangeIfChanged(callbackContext);
2397
+ const exportContext = this.buildExportServiceContext();
2398
+ try {
2399
+ downloadImageImpl(exportContext, options);
2400
+ }
2401
+ finally {
2402
+ this.operationGuard.endBusyOperation(operationToken);
2403
+ this.emitBusyChangeIfChanged(callbackContext);
2404
+ }
2405
+ }
2406
+ async exportImageBase64(options) {
2407
+ if (!this.canvas)
2408
+ return '';
2409
+ if (!this.canRunIdleOperation('exportImageBase64', options))
2410
+ return '';
2411
+ this.finalizeActiveTextEditingIfNeeded();
2412
+ const callbackContext = this.buildCallbackContext('exportImageBase64', false);
2413
+ const operationToken = this.operationGuard.beginBusyOperation('exportImageBase64');
2414
+ this.emitBusyChangeIfChanged(callbackContext);
2415
+ const exportContext = this.buildExportServiceContext();
2416
+ try {
2417
+ return await exportImageBase64Impl(exportContext, options);
2418
+ }
2419
+ finally {
2420
+ this.operationGuard.endBusyOperation(operationToken);
2421
+ this.emitBusyChangeIfChanged(callbackContext);
2422
+ }
2423
+ }
2424
+ async exportImageFile(options) {
2425
+ this.assertIdleForOperation('exportImageFile', options);
2426
+ this.finalizeActiveTextEditingIfNeeded();
1425
2427
  const callbackContext = this.buildCallbackContext('exportImageFile', false);
1426
2428
  const operationToken = this.operationGuard.beginBusyOperation('exportImageFile');
1427
2429
  this.emitBusyChangeIfChanged(callbackContext);
@@ -1437,7 +2439,7 @@ export class ImageEditor {
1437
2439
  buildExportServiceContext() {
1438
2440
  return {
1439
2441
  fabric: this.fabricModule,
1440
- canvas: this.canvas,
2442
+ canvas: this.getLiveCanvasOrThrow('export'),
1441
2443
  options: this.options,
1442
2444
  isImageLoaded: () => this.isImageLoaded(),
1443
2445
  getOriginalImage: () => this.originalImage,
@@ -1450,26 +2452,77 @@ export class ImageEditor {
1450
2452
  containerElement: this.containerElement,
1451
2453
  loadImage: async (base64, providedOptions) => {
1452
2454
  const geometry = this.captureImageDisplayGeometry();
1453
- await this.loadImage(base64, this.withInternalOperationOptions(operationToken, providedOptions));
2455
+ await this.loadImageInternal(base64, this.withInternalOperationOptions(operationToken, providedOptions !== null && providedOptions !== void 0 ? providedOptions : {}));
1454
2456
  this.restoreMergedImageDisplayGeometry(geometry);
1455
2457
  },
1456
- saveState: () => this.captureSnapshotInternal(),
2458
+ captureSnapshot: () => this.captureSnapshotInternal(),
1457
2459
  loadFromState: (snapshot) => this.loadFromStateInternal(snapshot, this.withInternalOperationOptions(operationToken, this.withAnimationQueueBypass())),
2460
+ exportImageBase64: (options) => exportImageBase64Impl(this.buildExportServiceContext(), options),
2461
+ updateUi: () => this.updateUi(),
2462
+ updateInputs: () => this.updateInputs(),
1458
2463
  removeAllMasksNoHistory: () => {
1459
2464
  const context = this.buildRemoveMaskContext();
1460
2465
  removeAllMasksImpl(context, { saveHistory: false });
1461
2466
  },
2467
+ getAnnotations: () => this.getAnnotations(),
2468
+ restoreAnnotations: (objects) => {
2469
+ const canvas = this.getLiveCanvasOrThrow('restoreAnnotations');
2470
+ objects.forEach((annotation) => {
2471
+ canvas.add(annotation);
2472
+ });
2473
+ syncAnnotationRuntimeStates(objects);
2474
+ attachTextEditingHandlersToAnnotations(this.buildTextControllerContext(), objects);
2475
+ this.annotationCounter = Math.max(this.annotationCounter, ...objects.map((annotation) => annotation.annotationId), 0);
2476
+ this.updateAnnotationList();
2477
+ },
2478
+ };
2479
+ }
2480
+ buildMergeAnnotationsContext(operationToken) {
2481
+ return {
2482
+ ...this.buildExportServiceContext(),
2483
+ historyManager: this.historyManager,
2484
+ containerElement: this.containerElement,
2485
+ loadImage: async (base64, providedOptions) => {
2486
+ const geometry = this.captureImageDisplayGeometry();
2487
+ await this.loadImageInternal(base64, this.withInternalOperationOptions(operationToken, providedOptions !== null && providedOptions !== void 0 ? providedOptions : {}));
2488
+ this.restoreMergedImageDisplayGeometry(geometry);
2489
+ },
2490
+ captureSnapshot: () => this.captureSnapshotInternal(),
2491
+ loadFromState: (snapshot) => this.loadFromStateInternal(snapshot, this.withInternalOperationOptions(operationToken, this.withAnimationQueueBypass())),
2492
+ exportImageBase64: (options) => exportImageBase64Impl(this.buildExportServiceContext(), options),
2493
+ updateUi: () => this.updateUi(),
2494
+ updateInputs: () => this.updateInputs(),
2495
+ removeAllAnnotationsNoHistory: () => {
2496
+ removeAllAnnotationsImpl(this.buildAnnotationManagerContext(), {
2497
+ saveHistory: false,
2498
+ force: true,
2499
+ });
2500
+ },
2501
+ getMasks: () => this.getMasks(),
2502
+ restoreMasks: (objects) => {
2503
+ const canvas = this.getLiveCanvasOrThrow('restoreMasks');
2504
+ objects.forEach((mask) => {
2505
+ canvas.add(mask);
2506
+ reattachMaskHoverHandlers(mask);
2507
+ });
2508
+ this.lastMask = objects.reduce((lastMask, mask) => !lastMask || mask.maskId > lastMask.maskId ? mask : lastMask, null);
2509
+ this.maskCounter = Math.max(this.maskCounter, ...objects.map((mask) => mask.maskId), 0);
2510
+ this.updateMaskList();
2511
+ },
1462
2512
  };
1463
2513
  }
1464
2514
  captureSnapshotInternal() {
1465
- var _a;
1466
- if (!this.canvas)
1467
- return '';
2515
+ var _a, _b;
2516
+ if (!this.canvas) {
2517
+ throw new Error('[ImageEditor] Cannot capture canvas snapshot before init or after dispose.');
2518
+ }
1468
2519
  const activeMask = this.getActiveMaskForSnapshot();
2520
+ const activeAnnotation = this.getActiveAnnotationForSnapshot();
1469
2521
  this.hideAllMaskLabels();
1470
2522
  return saveStateImpl({
1471
2523
  canvas: this.canvas,
1472
2524
  activeMaskId: (_a = activeMask === null || activeMask === void 0 ? void 0 : activeMask.maskId) !== null && _a !== void 0 ? _a : null,
2525
+ activeAnnotationId: (_b = activeAnnotation === null || activeAnnotation === void 0 ? void 0 : activeAnnotation.annotationId) !== null && _b !== void 0 ? _b : null,
1473
2526
  currentScale: this.currentScale,
1474
2527
  currentRotation: this.currentRotation,
1475
2528
  baseImageScale: this.baseImageScale,
@@ -1483,9 +2536,140 @@ export class ImageEditor {
1483
2536
  const activeObject = this.canvas.getActiveObject();
1484
2537
  if (activeObject && isMaskObject(activeObject))
1485
2538
  return activeObject;
1486
- return ((_a = this.canvas
2539
+ const labeledMasks = this.canvas
1487
2540
  .getObjects()
1488
- .find((object) => isMaskObject(object) && !!object.labelObject)) !== null && _a !== void 0 ? _a : null);
2541
+ .filter((object) => isMaskObject(object) && !!object.labelObject);
2542
+ return labeledMasks.length === 1 ? ((_a = labeledMasks[0]) !== null && _a !== void 0 ? _a : null) : null;
2543
+ }
2544
+ getActiveAnnotationForSnapshot() {
2545
+ if (!this.canvas)
2546
+ return null;
2547
+ const activeObject = this.canvas.getActiveObject();
2548
+ return activeObject && isAnnotationObject(activeObject) ? activeObject : null;
2549
+ }
2550
+ enterMosaicMode() {
2551
+ if (!this.canvas || !this.originalImage)
2552
+ return;
2553
+ if (this.mosaicSession)
2554
+ return;
2555
+ if (!this.isImageLoaded())
2556
+ return;
2557
+ if (!this.canRunIdleOperation('enterMosaicMode'))
2558
+ return;
2559
+ enterMosaicModeImpl(this.buildMosaicControllerContext());
2560
+ this.updateInputs();
2561
+ this.updateUi();
2562
+ const callbackContext = this.buildCallbackContext('enterMosaicMode', false);
2563
+ this.emitBusyChangeIfChanged(callbackContext);
2564
+ this.emitImageChanged(callbackContext);
2565
+ }
2566
+ exitMosaicMode() {
2567
+ if (!this.canvas || !this.mosaicSession)
2568
+ return;
2569
+ if (!this.canRunIdleOperation('exitMosaicMode'))
2570
+ return;
2571
+ exitMosaicModeImpl(this.buildMosaicControllerContext());
2572
+ this.updateInputs();
2573
+ this.updateUi();
2574
+ const callbackContext = this.buildCallbackContext('exitMosaicMode', false);
2575
+ this.emitBusyChangeIfChanged(callbackContext);
2576
+ this.emitImageChanged(callbackContext);
2577
+ }
2578
+ isMosaicMode() {
2579
+ return this.mosaicSession !== null;
2580
+ }
2581
+ getMosaicConfig() {
2582
+ return cloneResolvedMosaicConfig(this.currentMosaicConfig);
2583
+ }
2584
+ setMosaicConfig(config) {
2585
+ this.applyMosaicConfigPatch(config, 'setMosaicConfig');
2586
+ }
2587
+ resetMosaicConfig() {
2588
+ if (this.isDisposed)
2589
+ return;
2590
+ const nextConfig = cloneResolvedMosaicConfig(this.defaultMosaicConfig);
2591
+ if (areResolvedMosaicConfigsEqual(this.currentMosaicConfig, nextConfig))
2592
+ return;
2593
+ this.currentMosaicConfig = nextConfig;
2594
+ if (this.mosaicSession && this.canvas) {
2595
+ updateMosaicPreview(this.buildMosaicControllerContext());
2596
+ }
2597
+ this.updateInputs();
2598
+ this.updateUi();
2599
+ this.emitImageChanged(this.buildCallbackContext('resetMosaicConfig', false));
2600
+ }
2601
+ setMosaicBrushSize(size) {
2602
+ this.applyMosaicConfigPatch({ brushSize: size }, 'setMosaicBrushSize');
2603
+ }
2604
+ setMosaicBlockSize(size) {
2605
+ this.applyMosaicConfigPatch({ blockSize: size }, 'setMosaicBlockSize');
2606
+ }
2607
+ applyMosaicConfigPatch(config, operation) {
2608
+ if (this.isDisposed)
2609
+ return;
2610
+ if (config === null || typeof config !== 'object' || Array.isArray(config)) {
2611
+ reportWarning(this.options, new TypeError('[ImageEditor] Invalid Mosaic config object.'), 'Ignored invalid Mosaic config.');
2612
+ return;
2613
+ }
2614
+ const invalidFields = getInvalidMosaicConfigFields(config);
2615
+ if (invalidFields.length > 0) {
2616
+ reportWarning(this.options, new TypeError(`[ImageEditor] Ignored invalid Mosaic config field(s): ` +
2617
+ `${invalidFields.join(', ')}.`), 'Ignored invalid Mosaic config fields.');
2618
+ }
2619
+ const nextConfig = mergeMosaicConfigPatch(this.currentMosaicConfig, config);
2620
+ if (areResolvedMosaicConfigsEqual(this.currentMosaicConfig, nextConfig))
2621
+ return;
2622
+ this.currentMosaicConfig = nextConfig;
2623
+ if (this.mosaicSession && this.canvas) {
2624
+ updateMosaicPreview(this.buildMosaicControllerContext());
2625
+ }
2626
+ this.updateInputs();
2627
+ this.updateUi();
2628
+ this.emitImageChanged(this.buildCallbackContext(operation, false));
2629
+ }
2630
+ buildMosaicControllerContext() {
2631
+ return {
2632
+ fabric: this.fabricModule,
2633
+ canvas: this.getLiveCanvasOrThrow('mosaicController'),
2634
+ options: this.options,
2635
+ historyManager: this.historyManager,
2636
+ getMosaicConfig: () => cloneResolvedMosaicConfig(this.currentMosaicConfig),
2637
+ isImageLoaded: () => this.isImageLoaded(),
2638
+ getOriginalImage: () => this.originalImage,
2639
+ setOriginalImage: (image) => {
2640
+ this.originalImage = image;
2641
+ },
2642
+ getCurrentImageMimeType: () => this.currentImageMimeType,
2643
+ setCurrentImageMimeType: (mimeType) => {
2644
+ this.currentImageMimeType = mimeType;
2645
+ },
2646
+ getLastSnapshot: () => this.lastSnapshot,
2647
+ setLastSnapshot: (snapshot) => {
2648
+ this.lastSnapshot = snapshot;
2649
+ },
2650
+ captureSnapshot: () => this.captureSnapshotInternal(),
2651
+ loadFromState: (snapshot) => this.loadFromStateInternal(snapshot, this.withAnimationQueueBypass()),
2652
+ updateUi: () => {
2653
+ this.updateUi();
2654
+ },
2655
+ updateInputs: () => {
2656
+ this.updateInputs();
2657
+ },
2658
+ hideAllMaskLabels: () => {
2659
+ this.hideAllMaskLabels();
2660
+ },
2661
+ emitImageChanged: (context) => {
2662
+ this.emitImageChanged(context);
2663
+ },
2664
+ emitBusyChangeIfChanged: (context) => {
2665
+ this.emitBusyChangeIfChanged(context);
2666
+ },
2667
+ buildCallbackContext: (operation, isInternal) => this.buildCallbackContext(operation, isInternal),
2668
+ getMosaicSession: () => this.mosaicSession,
2669
+ setMosaicSession: (session) => {
2670
+ this.mosaicSession = session;
2671
+ },
2672
+ };
1489
2673
  }
1490
2674
  enterCropMode() {
1491
2675
  if (!this.canvas || !this.originalImage)
@@ -1546,7 +2730,7 @@ export class ImageEditor {
1546
2730
  buildCropControllerContext(operationToken) {
1547
2731
  return {
1548
2732
  fabric: this.fabricModule,
1549
- canvas: this.canvas,
2733
+ canvas: this.getLiveCanvasOrThrow('cropController'),
1550
2734
  options: this.options,
1551
2735
  historyManager: this.historyManager,
1552
2736
  isImageLoaded: () => this.isImageLoaded(),
@@ -1558,7 +2742,7 @@ export class ImageEditor {
1558
2742
  },
1559
2743
  saveState: () => this.captureSnapshotInternal(),
1560
2744
  loadFromState: (snapshot) => this.loadFromStateInternal(snapshot, this.withInternalOperationOptions(operationToken, this.withAnimationQueueBypass())),
1561
- loadImage: (base64, providedOptions) => this.loadImage(base64, this.withInternalOperationOptions(operationToken, providedOptions)),
2745
+ loadImage: (base64, providedOptions) => this.loadImageInternal(base64, this.withInternalOperationOptions(operationToken, providedOptions !== null && providedOptions !== void 0 ? providedOptions : {})),
1562
2746
  getMaskCounter: () => this.maskCounter,
1563
2747
  setMaskCounter: (n) => {
1564
2748
  this.maskCounter = n;
@@ -1568,33 +2752,131 @@ export class ImageEditor {
1568
2752
  },
1569
2753
  };
1570
2754
  }
2755
+ syncInputValue(inputElement, value) {
2756
+ if (!inputElement)
2757
+ return;
2758
+ const ownerDocument = inputElement.ownerDocument;
2759
+ if (ownerDocument.activeElement === inputElement && !inputElement.readOnly)
2760
+ return;
2761
+ if (inputElement.value !== value)
2762
+ inputElement.value = value;
2763
+ }
1571
2764
  updateInputs() {
1572
2765
  const scaleId = this.elements.scalePercentageInput;
1573
- if (!scaleId)
2766
+ if (scaleId) {
2767
+ const scaleInputElement = document.getElementById(scaleId);
2768
+ this.syncInputValue(scaleInputElement, String(Math.round(this.currentScale * 100)));
2769
+ }
2770
+ const mosaicConfig = this.getMosaicConfig();
2771
+ const mosaicBrushSizeInputId = this.elements.mosaicBrushSizeInput;
2772
+ if (mosaicBrushSizeInputId) {
2773
+ const brushInput = document.getElementById(mosaicBrushSizeInputId);
2774
+ this.syncInputValue(brushInput, String(mosaicConfig.brushSize));
2775
+ }
2776
+ const mosaicBlockSizeInputId = this.elements.mosaicBlockSizeInput;
2777
+ if (mosaicBlockSizeInputId) {
2778
+ const blockInput = document.getElementById(mosaicBlockSizeInputId);
2779
+ this.syncInputValue(blockInput, String(mosaicConfig.blockSize));
2780
+ }
2781
+ const textConfig = this.getTextConfig();
2782
+ const textColorInputId = this.elements.textColorInput;
2783
+ if (textColorInputId) {
2784
+ const textColorInput = document.getElementById(textColorInputId);
2785
+ this.syncInputValue(textColorInput, textConfig.fill);
2786
+ }
2787
+ const textFontSizeInputId = this.elements.textFontSizeInput;
2788
+ if (textFontSizeInputId) {
2789
+ const fontInput = document.getElementById(textFontSizeInputId);
2790
+ this.syncInputValue(fontInput, String(textConfig.fontSize));
2791
+ }
2792
+ const drawConfig = this.getDrawConfig();
2793
+ const drawColorInputId = this.elements.drawColorInput;
2794
+ if (drawColorInputId) {
2795
+ const drawColorInput = document.getElementById(drawColorInputId);
2796
+ this.syncInputValue(drawColorInput, drawConfig.color);
2797
+ }
2798
+ const drawBrushSizeInputId = this.elements.drawBrushSizeInput;
2799
+ if (drawBrushSizeInputId) {
2800
+ const brushInput = document.getElementById(drawBrushSizeInputId);
2801
+ this.syncInputValue(brushInput, String(drawConfig.brushSize));
2802
+ }
2803
+ }
2804
+ async mergeAnnotations() {
2805
+ if (!this.canvas)
2806
+ return;
2807
+ if (!this.canRunIdleOperation('mergeAnnotations'))
1574
2808
  return;
1575
- const scaleInputElement = document.getElementById(scaleId);
1576
- if (scaleInputElement)
1577
- scaleInputElement.value = String(Math.round(this.currentScale * 100));
2809
+ this.finalizeActiveTextEditingIfNeeded();
2810
+ const hasAnnotations = this.canvas.getObjects().some(isAnnotationObject);
2811
+ if (!hasAnnotations)
2812
+ return;
2813
+ const callbackContext = this.buildCallbackContext('mergeAnnotations', false);
2814
+ const operationToken = this.operationGuard.beginBusyOperation('mergeAnnotations');
2815
+ this.emitBusyChangeIfChanged(callbackContext);
2816
+ this.updateUi();
2817
+ try {
2818
+ await mergeAnnotationsImpl(this.buildMergeAnnotationsContext(operationToken));
2819
+ this.updateInputs();
2820
+ this.updateMaskList();
2821
+ this.updateAnnotationList();
2822
+ this.emitAnnotationsChanged(callbackContext);
2823
+ if (this.getMasks().length > 0)
2824
+ this.emitMasksChanged(callbackContext);
2825
+ this.emitImageChanged(callbackContext);
2826
+ }
2827
+ finally {
2828
+ this.operationGuard.endBusyOperation(operationToken);
2829
+ this.emitBusyChangeIfChanged(callbackContext);
2830
+ this.updateUi();
2831
+ }
1578
2832
  }
1579
2833
  updateUi() {
2834
+ var _a;
1580
2835
  if (!this.canvas)
1581
2836
  return;
1582
2837
  const hasImage = !!this.originalImage;
1583
2838
  const masks = hasImage ? this.canvas.getObjects().filter(isMaskObject) : [];
2839
+ const annotations = hasImage ? this.canvas.getObjects().filter(isAnnotationObject) : [];
1584
2840
  const hasMasks = masks.length > 0;
2841
+ const hasAnnotations = annotations.length > 0;
1585
2842
  const activeObject = this.canvas.getActiveObject();
1586
2843
  const hasSelectedMask = !!(activeObject && isMaskObject(activeObject));
2844
+ const hasSelectedAnnotation = !!(activeObject && isAnnotationObject(activeObject));
2845
+ const hasSelectedEditableObject = !!activeObject && isEditableOverlayObject(activeObject);
1587
2846
  const isDefaultTransform = this.currentScale === 1 && this.currentRotation === 0;
1588
2847
  const canUndo = this.historyManager.canUndo();
1589
2848
  const canRedo = this.historyManager.canRedo();
1590
2849
  const isInCropMode = this.cropSession !== null;
2850
+ const isInMosaicMode = this.mosaicSession !== null;
2851
+ const isInTextMode = this.textSession !== null;
2852
+ const isInDrawMode = this.drawSession !== null;
1591
2853
  const isBusy = this.operationGuard.isBusy() || this.animQueue.isBusy();
2854
+ const isMosaicApplying = ((_a = this.mosaicSession) === null || _a === void 0 ? void 0 : _a.isApplying) === true;
1592
2855
  if (isInCropMode) {
1593
2856
  CROP_MODE_CONTROL_KEYS.forEach((key) => {
1594
2857
  this.setControlEnabled(key, !isBusy && CROP_MODE_ENABLED_KEYS.includes(key));
1595
2858
  });
1596
2859
  return;
1597
2860
  }
2861
+ if (isInTextMode) {
2862
+ CROP_MODE_CONTROL_KEYS.forEach((key) => {
2863
+ this.setControlEnabled(key, !isBusy && TEXT_MODE_ENABLED_KEYS.includes(key));
2864
+ });
2865
+ return;
2866
+ }
2867
+ if (isInDrawMode) {
2868
+ CROP_MODE_CONTROL_KEYS.forEach((key) => {
2869
+ this.setControlEnabled(key, !isBusy && DRAW_MODE_ENABLED_KEYS.includes(key));
2870
+ });
2871
+ return;
2872
+ }
2873
+ if (isInMosaicMode) {
2874
+ MOSAIC_MODE_CONTROL_KEYS.forEach((key) => {
2875
+ this.setControlEnabled(key, !isBusy && !isMosaicApplying && MOSAIC_MODE_ENABLED_KEYS.includes(key));
2876
+ });
2877
+ this.setControlEnabled('imageInput', false);
2878
+ return;
2879
+ }
1598
2880
  this.setControlEnabled('scalePercentageInput', hasImage && !isBusy);
1599
2881
  this.setControlEnabled('rotateLeftDegreesInput', hasImage && !isBusy);
1600
2882
  this.setControlEnabled('rotateRightDegreesInput', hasImage && !isBusy);
@@ -1606,11 +2888,31 @@ export class ImageEditor {
1606
2888
  this.setControlEnabled('removeSelectedMaskButton', hasSelectedMask && !isBusy);
1607
2889
  this.setControlEnabled('removeAllMasksButton', hasMasks && !isBusy);
1608
2890
  this.setControlEnabled('mergeMasksButton', hasImage && hasMasks && !isBusy);
2891
+ this.setControlEnabled('removeSelectedAnnotationButton', hasSelectedAnnotation && !isBusy);
2892
+ this.setControlEnabled('removeAllAnnotationsButton', hasAnnotations && !isBusy);
2893
+ this.setControlEnabled('deleteSelectedObjectButton', hasSelectedEditableObject && !isBusy);
2894
+ this.setControlEnabled('mergeAnnotationsButton', hasImage && hasAnnotations && !isBusy);
2895
+ this.setControlEnabled('bringSelectedObjectForwardButton', hasSelectedEditableObject && !isBusy);
2896
+ this.setControlEnabled('sendSelectedObjectBackwardButton', hasSelectedEditableObject && !isBusy);
2897
+ this.setControlEnabled('bringSelectedObjectToFrontButton', hasSelectedEditableObject && !isBusy);
2898
+ this.setControlEnabled('sendSelectedObjectToBackButton', hasSelectedEditableObject && !isBusy);
1609
2899
  this.setControlEnabled('downloadImageButton', hasImage && !isBusy);
1610
2900
  this.setControlEnabled('resetImageTransformButton', hasImage && !isDefaultTransform && !isBusy);
1611
2901
  this.setControlEnabled('undoButton', hasImage && !isBusy && canUndo);
1612
2902
  this.setControlEnabled('redoButton', hasImage && !isBusy && canRedo);
1613
2903
  this.setControlEnabled('enterCropModeButton', hasImage && !isBusy);
2904
+ this.setControlEnabled('enterMosaicModeButton', hasImage && !isBusy);
2905
+ this.setControlEnabled('enterTextModeButton', hasImage && !isBusy);
2906
+ this.setControlEnabled('enterDrawModeButton', hasImage && !isBusy);
2907
+ this.setControlEnabled('exitMosaicModeButton', false);
2908
+ this.setControlEnabled('exitTextModeButton', false);
2909
+ this.setControlEnabled('exitDrawModeButton', false);
2910
+ this.setControlEnabled('mosaicBrushSizeInput', !this.isDisposed);
2911
+ this.setControlEnabled('mosaicBlockSizeInput', !this.isDisposed);
2912
+ this.setControlEnabled('textColorInput', !this.isDisposed);
2913
+ this.setControlEnabled('textFontSizeInput', !this.isDisposed);
2914
+ this.setControlEnabled('drawColorInput', !this.isDisposed);
2915
+ this.setControlEnabled('drawBrushSizeInput', !this.isDisposed);
1614
2916
  this.setControlEnabled('imageInput', !isBusy);
1615
2917
  this.setControlEnabled('applyCropButton', false);
1616
2918
  this.setControlEnabled('cancelCropButton', false);
@@ -1625,7 +2927,10 @@ export class ImageEditor {
1625
2927
  return;
1626
2928
  this.recordElementOriginalState(key, controlElement);
1627
2929
  if ('disabled' in controlElement) {
1628
- controlElement.disabled = !isEnabled;
2930
+ const formControl = controlElement;
2931
+ const nextDisabled = !isEnabled;
2932
+ if (formControl.disabled !== nextDisabled)
2933
+ formControl.disabled = nextDisabled;
1629
2934
  return;
1630
2935
  }
1631
2936
  if (!isEnabled) {
@@ -1698,6 +3003,15 @@ export class ImageEditor {
1698
3003
  this.operationGuard.markDisposed();
1699
3004
  this.animQueue.clear();
1700
3005
  (_a = this.domBindings) === null || _a === void 0 ? void 0 : _a.removeAll();
3006
+ if (this.keyboardHandler && this.keyboardDocument) {
3007
+ try {
3008
+ this.keyboardDocument.removeEventListener('keydown', this.keyboardHandler);
3009
+ }
3010
+ catch {
3011
+ }
3012
+ }
3013
+ this.keyboardHandler = null;
3014
+ this.keyboardDocument = null;
1701
3015
  this.restoreElementOriginalStates();
1702
3016
  if (this.cropSession && this.canvas) {
1703
3017
  try {
@@ -1708,6 +3022,30 @@ export class ImageEditor {
1708
3022
  }
1709
3023
  this.cropSession = null;
1710
3024
  }
3025
+ if (this.mosaicSession && this.canvas) {
3026
+ try {
3027
+ exitMosaicModeImpl(this.buildMosaicControllerContext());
3028
+ }
3029
+ catch {
3030
+ }
3031
+ this.mosaicSession = null;
3032
+ }
3033
+ if (this.textSession && this.canvas) {
3034
+ try {
3035
+ exitTextModeImpl(this.buildTextControllerContext());
3036
+ }
3037
+ catch {
3038
+ }
3039
+ this.textSession = null;
3040
+ }
3041
+ if (this.drawSession && this.canvas) {
3042
+ try {
3043
+ exitDrawModeImpl(this.buildDrawControllerContext());
3044
+ }
3045
+ catch {
3046
+ }
3047
+ this.drawSession = null;
3048
+ }
1711
3049
  if (this.canvas) {
1712
3050
  try {
1713
3051
  void Promise.resolve(this.canvas.dispose()).catch(() => {
@@ -1723,6 +3061,7 @@ export class ImageEditor {
1723
3061
  this.currentImageMimeType = null;
1724
3062
  this.lastMask = null;
1725
3063
  this.maskCounter = 0;
3064
+ this.annotationCounter = 0;
1726
3065
  this.currentScale = 1;
1727
3066
  this.currentRotation = 0;
1728
3067
  this.baseImageScale = 1;