@bensitu/image-editor 1.5.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/README.md +460 -509
  2. package/dist/cjs/index.cjs +6892 -0
  3. package/dist/cjs/index.cjs.map +1 -0
  4. package/dist/esm/animation/animation-queue.js +74 -0
  5. package/dist/esm/animation/animation-queue.js.map +1 -0
  6. package/dist/esm/core/callback-reporter.js +23 -0
  7. package/dist/esm/core/callback-reporter.js.map +1 -0
  8. package/dist/esm/core/default-options.js +529 -0
  9. package/dist/esm/core/default-options.js.map +1 -0
  10. package/dist/esm/core/errors.js +156 -0
  11. package/dist/esm/core/errors.js.map +1 -0
  12. package/dist/esm/core/operation-guard.js +157 -0
  13. package/dist/esm/core/operation-guard.js.map +1 -0
  14. package/dist/esm/core/public-types.js +4 -0
  15. package/dist/esm/core/public-types.js.map +1 -0
  16. package/dist/esm/core/state-serializer.js +252 -0
  17. package/dist/esm/core/state-serializer.js.map +1 -0
  18. package/dist/esm/crop/crop-controller.js +405 -0
  19. package/dist/esm/crop/crop-controller.js.map +1 -0
  20. package/dist/esm/export/export-format.js +53 -0
  21. package/dist/esm/export/export-format.js.map +1 -0
  22. package/dist/esm/export/export-service.js +607 -0
  23. package/dist/esm/export/export-service.js.map +1 -0
  24. package/dist/esm/fabric/fabric-adapter.js +37 -0
  25. package/dist/esm/fabric/fabric-adapter.js.map +1 -0
  26. package/dist/esm/fabric/fabric-animation.js +89 -0
  27. package/dist/esm/fabric/fabric-animation.js.map +1 -0
  28. package/dist/esm/history/command.js +2 -0
  29. package/dist/esm/history/command.js.map +1 -0
  30. package/dist/esm/history/history-manager.js +103 -0
  31. package/dist/esm/history/history-manager.js.map +1 -0
  32. package/dist/esm/image/image-loader.js +238 -0
  33. package/dist/esm/image/image-loader.js.map +1 -0
  34. package/dist/esm/image/image-resampler.js +60 -0
  35. package/dist/esm/image/image-resampler.js.map +1 -0
  36. package/dist/esm/image/layout-manager.js +206 -0
  37. package/dist/esm/image/layout-manager.js.map +1 -0
  38. package/dist/esm/image/transform-controller.js +132 -0
  39. package/dist/esm/image/transform-controller.js.map +1 -0
  40. package/dist/esm/image-editor.js +2076 -0
  41. package/dist/esm/image-editor.js.map +1 -0
  42. package/dist/esm/index.js +5 -0
  43. package/dist/esm/index.js.map +1 -0
  44. package/dist/esm/mask/mask-factory.js +356 -0
  45. package/dist/esm/mask/mask-factory.js.map +1 -0
  46. package/dist/esm/mask/mask-label-manager.js +120 -0
  47. package/dist/esm/mask/mask-label-manager.js.map +1 -0
  48. package/dist/esm/mask/mask-list.js +53 -0
  49. package/dist/esm/mask/mask-list.js.map +1 -0
  50. package/dist/esm/mask/mask-style.js +182 -0
  51. package/dist/esm/mask/mask-style.js.map +1 -0
  52. package/dist/esm/mosaic/mosaic-controller.js +670 -0
  53. package/dist/esm/mosaic/mosaic-controller.js.map +1 -0
  54. package/dist/esm/mosaic/mosaic-geometry.js +81 -0
  55. package/dist/esm/mosaic/mosaic-geometry.js.map +1 -0
  56. package/dist/esm/mosaic/mosaic-pixelate.js +71 -0
  57. package/dist/esm/mosaic/mosaic-pixelate.js.map +1 -0
  58. package/dist/esm/ui/dom-bindings.js +67 -0
  59. package/dist/esm/ui/dom-bindings.js.map +1 -0
  60. package/dist/esm/ui/ui-state.js +25 -0
  61. package/dist/esm/ui/ui-state.js.map +1 -0
  62. package/dist/esm/ui/visibility-state.js +11 -0
  63. package/dist/esm/ui/visibility-state.js.map +1 -0
  64. package/dist/esm/utils/canvas-region.js +100 -0
  65. package/dist/esm/utils/canvas-region.js.map +1 -0
  66. package/dist/esm/utils/dom.js +6 -0
  67. package/dist/esm/utils/dom.js.map +1 -0
  68. package/dist/esm/utils/file.js +53 -0
  69. package/dist/esm/utils/file.js.map +1 -0
  70. package/dist/esm/utils/number.js +24 -0
  71. package/dist/esm/utils/number.js.map +1 -0
  72. package/dist/esm/utils/timeout.js +17 -0
  73. package/dist/esm/utils/timeout.js.map +1 -0
  74. package/dist/types/animation/animation-queue.d.ts +111 -0
  75. package/dist/types/animation/animation-queue.d.ts.map +1 -0
  76. package/dist/types/core/callback-reporter.d.ts +125 -0
  77. package/dist/types/core/callback-reporter.d.ts.map +1 -0
  78. package/dist/types/core/default-options.d.ts +84 -0
  79. package/dist/types/core/default-options.d.ts.map +1 -0
  80. package/dist/types/core/errors.d.ts +142 -0
  81. package/dist/types/core/errors.d.ts.map +1 -0
  82. package/dist/types/core/operation-guard.d.ts +194 -0
  83. package/dist/types/core/operation-guard.d.ts.map +1 -0
  84. package/dist/types/core/public-types.d.ts +788 -0
  85. package/dist/types/core/public-types.d.ts.map +1 -0
  86. package/dist/types/core/state-serializer.d.ts +303 -0
  87. package/dist/types/core/state-serializer.d.ts.map +1 -0
  88. package/dist/types/crop/crop-controller.d.ts +407 -0
  89. package/dist/types/crop/crop-controller.d.ts.map +1 -0
  90. package/dist/types/export/export-format.d.ts +136 -0
  91. package/dist/types/export/export-format.d.ts.map +1 -0
  92. package/dist/types/export/export-service.d.ts +333 -0
  93. package/dist/types/export/export-service.d.ts.map +1 -0
  94. package/dist/types/fabric/fabric-adapter.d.ts +74 -0
  95. package/dist/types/fabric/fabric-adapter.d.ts.map +1 -0
  96. package/dist/types/fabric/fabric-animation.d.ts +141 -0
  97. package/dist/types/fabric/fabric-animation.d.ts.map +1 -0
  98. package/dist/types/history/command.d.ts +16 -0
  99. package/dist/types/history/command.d.ts.map +1 -0
  100. package/dist/types/history/history-manager.d.ts +129 -0
  101. package/dist/types/history/history-manager.d.ts.map +1 -0
  102. package/dist/types/image/image-loader.d.ts +263 -0
  103. package/dist/types/image/image-loader.d.ts.map +1 -0
  104. package/dist/types/image/image-resampler.d.ts +139 -0
  105. package/dist/types/image/image-resampler.d.ts.map +1 -0
  106. package/dist/types/image/layout-manager.d.ts +211 -0
  107. package/dist/types/image/layout-manager.d.ts.map +1 -0
  108. package/dist/types/image/transform-controller.d.ts +286 -0
  109. package/dist/types/image/transform-controller.d.ts.map +1 -0
  110. package/dist/types/image-editor.d.ts +661 -0
  111. package/dist/types/image-editor.d.ts.map +1 -0
  112. package/dist/types/index.d.cts +31 -0
  113. package/dist/types/index.d.cts.map +1 -0
  114. package/dist/types/index.d.ts +31 -0
  115. package/dist/types/index.d.ts.map +1 -0
  116. package/dist/types/mask/mask-factory.d.ts +212 -0
  117. package/dist/types/mask/mask-factory.d.ts.map +1 -0
  118. package/dist/types/mask/mask-label-manager.d.ts +171 -0
  119. package/dist/types/mask/mask-label-manager.d.ts.map +1 -0
  120. package/dist/types/mask/mask-list.d.ts +144 -0
  121. package/dist/types/mask/mask-list.d.ts.map +1 -0
  122. package/dist/types/mask/mask-style.d.ts +338 -0
  123. package/dist/types/mask/mask-style.d.ts.map +1 -0
  124. package/dist/types/mosaic/mosaic-controller.d.ts +82 -0
  125. package/dist/types/mosaic/mosaic-controller.d.ts.map +1 -0
  126. package/dist/types/mosaic/mosaic-geometry.d.ts +29 -0
  127. package/dist/types/mosaic/mosaic-geometry.d.ts.map +1 -0
  128. package/dist/types/mosaic/mosaic-pixelate.d.ts +23 -0
  129. package/dist/types/mosaic/mosaic-pixelate.d.ts.map +1 -0
  130. package/dist/types/ui/dom-bindings.d.ts +105 -0
  131. package/dist/types/ui/dom-bindings.d.ts.map +1 -0
  132. package/dist/types/ui/ui-state.d.ts +112 -0
  133. package/dist/types/ui/ui-state.d.ts.map +1 -0
  134. package/dist/types/ui/visibility-state.d.ts +77 -0
  135. package/dist/types/ui/visibility-state.d.ts.map +1 -0
  136. package/dist/types/utils/canvas-region.d.ts +177 -0
  137. package/dist/types/utils/canvas-region.d.ts.map +1 -0
  138. package/dist/types/utils/dom.d.ts +26 -0
  139. package/dist/types/utils/dom.d.ts.map +1 -0
  140. package/dist/types/utils/file.d.ts +80 -0
  141. package/dist/types/utils/file.d.ts.map +1 -0
  142. package/dist/types/utils/number.d.ts +131 -0
  143. package/dist/types/utils/number.d.ts.map +1 -0
  144. package/dist/types/utils/timeout.d.ts +84 -0
  145. package/dist/types/utils/timeout.d.ts.map +1 -0
  146. package/dist/umd/image-editor.umd.js +2 -0
  147. package/dist/umd/image-editor.umd.js.map +1 -0
  148. package/package.json +72 -66
  149. package/dist/image-editor.cjs +0 -4407
  150. package/dist/image-editor.cjs.map +0 -7
  151. package/dist/image-editor.esm.js +0 -4376
  152. package/dist/image-editor.esm.js.map +0 -7
  153. package/dist/image-editor.esm.min.js +0 -9
  154. package/dist/image-editor.esm.min.js.map +0 -7
  155. package/dist/image-editor.esm.min.mjs +0 -9
  156. package/dist/image-editor.esm.min.mjs.map +0 -7
  157. package/dist/image-editor.esm.mjs +0 -4376
  158. package/dist/image-editor.esm.mjs.map +0 -7
  159. package/dist/image-editor.js +0 -4373
  160. package/dist/image-editor.js.map +0 -7
  161. package/dist/image-editor.min.js +0 -9
  162. package/dist/image-editor.min.js.map +0 -7
  163. package/image-editor.d.ts +0 -271
  164. package/src/browser.js +0 -11
  165. package/src/esm.js +0 -9
  166. package/src/image-editor.js +0 -5013
@@ -0,0 +1,333 @@
1
+ /**
2
+ * Base64, file, and download entry points for the current export
3
+ * pipeline. The orchestrator (`image-editor.ts`) delegates
4
+ * `exportImageBase64`, `exportImageFile`, and `downloadImage`
5
+ * to the helpers in this module so the export logic lives in
6
+ * a single owner module per the documented module-decomposition
7
+ * table.
8
+ *
9
+ * ## Owned contracts
10
+ *
11
+ * - Before computing the export region, every
12
+ * export entry point SHALL discard any active Fabric `ActiveSelection`
13
+ * so it is not serialized into the output. The discard is performed
14
+ * unconditionally; calling `canvas.discardActiveObject` with no active
15
+ * selection is a documented no-op.
16
+ * - `exportImageBase64(options?: Base64ExportOptions)`
17
+ * is the only canonical base64 export entry point. It accepts both
18
+ * `fileType` and `format` for ergonomic interop and
19
+ * returns a `Promise<string>` resolving to a `data:image/...;base64...`
20
+ * data URL.
21
+ * - `exportImageFile(options?: ImageFileExportOptions)`
22
+ * resolves to a `File` whose name comes from `options.fileName` or the
23
+ * editor's `defaultDownloadFileName`.
24
+ * - `downloadImage(fileName?: string)` triggers a
25
+ * browser download with the resolved filename. The bytes match the same
26
+ * pipeline used by `exportImageBase64`.
27
+ * - When `isImageLoaded` is `false`, the three
28
+ * entry points exhibit the documented "no image loaded" shapes:
29
+ *
30
+ * | entry point | shape on no image |
31
+ * | -------------------- | ----------------------------------- |
32
+ * | `exportImageBase64` | resolves to `''` |
33
+ * | `exportImageFile` | rejects with `ExportNotReadyError` |
34
+ * | `downloadImage` | no-op (returns synchronously) |
35
+ *
36
+ * Each path emits a single `console.warn` naming the missing image so
37
+ * the consumer's logs identify which export attempt was skipped.
38
+ * - When `exportArea` resolves
39
+ * to `'image'` and a valid `originalImage` exists, the export region is
40
+ * computed from `originalImage.getBoundingRect` and passed directly
41
+ * as `left`/`top`/`width`/`height` to Fabric's `toDataURL` options.
42
+ * No intermediate `<canvas>` element is created, and sub-pixel
43
+ * width/height values are floored to integer pixels through the
44
+ * {@link floorRegion} helper before Fabric receives the region.
45
+ * - When `mergeMask` is
46
+ * `true`, every mask's live style (`opacity`, `fill`, `stroke`,
47
+ * `strokeWidth`, `selectable`, `lockRotation`) is captured BEFORE the
48
+ * mutator forces the bake-in style (`opacity: 1, fill: '#000',
49
+ * strokeWidth: 0, stroke: null, selectable: false`) and restored
50
+ * inside a `finally` block whether the inner render resolved or
51
+ * rejected. The backup/restore bracket is owned by
52
+ * {@link withMaskStyleBackup} in `mask/mask-style.ts`; this module
53
+ * only contributes the bake-in mutator.
54
+ * - **mergeMasks pre-export** — Before computing the merged
55
+ * bitmap, {@link mergeMasks} discards any active Fabric
56
+ * `ActiveSelection`. {@link exportImageBase64} also discards on its
57
+ * own entry, so the discard runs at most twice (both calls are
58
+ * idempotent no-ops when nothing is selected).
59
+ * - **mergeMasks atomicity** —
60
+ * {@link mergeMasks} is the canonical merge entry point
61
+ * (`Promise<void>`). It captures a pre-merge snapshot suitable for
62
+ * `loadFromState`, renders the merged bitmap via
63
+ * {@link exportImageBase64}, removes every mask without history,
64
+ * reloads the merged data URL through the transactional
65
+ * `image/image-loader.ts`, and on success pushes exactly one
66
+ * {@link Command} whose `undo` restores the pre-merge snapshot and
67
+ * whose `execute` re-applies the merged image. On any failure
68
+ * between snapshot capture and history push, the pre-merge snapshot
69
+ * is restored and the promise rejects with
70
+ * {@link MergeMasksError}. Container scroll position is preserved
71
+ * across the success path (canonically via
72
+ * `loadImage(..., { preserveScroll: true})`, with a defensive
73
+ * restore at the tail of the merge).
74
+ *
75
+ * ## Why a service-shaped module
76
+ *
77
+ * Per the documented "Mapping Contracts to modules" table the export
78
+ * pipeline owns its own module so the orchestrator stays thin. The
79
+ * service is a stateless function-collection (matching
80
+ * `image/image-loader.ts` and `core/state-serializer.ts`) and reads every
81
+ * editor field through an explicit {@link ExportServiceContext} bundle.
82
+ * This keeps the orchestrator authoritative for editor state — the export
83
+ * helpers never store a reference to the canvas or options between
84
+ * invocations — and makes the module trivially mockable from unit and
85
+ * property tests.
86
+ *
87
+ * The module is intentionally NOT re-exported from `src/index.ts`
88
+ * (only `ImageEditor`, `isMaskObject`, and the
89
+ * documented public types are root-exported).
90
+ *
91
+ * @module
92
+ */
93
+ import type * as FabricNS from 'fabric';
94
+ import type { Base64ExportOptions, FabricModule, ImageFileExportOptions, LoadImageOptions, ResolvedOptions } from '../core/public-types.js';
95
+ import { type HistoryManager } from '../history/history-manager.js';
96
+ /**
97
+ * Dependency bundle passed by the `ImageEditor` facade into every export
98
+ * entry point. The service has no class state of its own — every editor
99
+ * field it reads is exposed here as a value or callback so the facade
100
+ * keeps ownership of the canonical state.
101
+ *
102
+ * Mirrors the shape of {@link import('../image/image-loader.js').LoadImageContext}
103
+ * for consistency across pipeline modules.
104
+ *
105
+ * @see image/image-loader.ts (the same context-bundle pattern)
106
+ */
107
+ export interface ExportServiceContext {
108
+ /** The Fabric module providing `Canvas` / `FabricImage`. */
109
+ readonly fabric: FabricModule;
110
+ /** The live Fabric canvas. Always non-null on a constructed editor. */
111
+ readonly canvas: FabricNS.Canvas;
112
+ /** Resolved editor options — supplies `defaultDownloadFileName`,
113
+ * `downsampleQuality`, `exportMultiplier`, and
114
+ * `exportAreaByDefault`. */
115
+ readonly options: ResolvedOptions;
116
+ /**
117
+ * Predicate matching `ImageEditor.isImageLoaded`. Returns `true`
118
+ * only when an `originalImage` has been committed and has positive
119
+ * dimensions (reads through this gate).
120
+ */
121
+ isImageLoaded(): boolean;
122
+ /**
123
+ * The currently committed `originalImage`, or `null` when no image is
124
+ * loaded. {@link computeExportRegion} reads it through this callback
125
+ * to derive the floored bounding box for image-area
126
+ * exports. When the image has been disposed or
127
+ * never loaded the seam falls through to a full-canvas export.
128
+ */
129
+ getOriginalImage(): FabricNS.FabricImage | null;
130
+ }
131
+ /**
132
+ * Render the live canvas to a base64 data URL.
133
+ *
134
+ * Steps, in order:
135
+ *
136
+ * 1. **No-image gate** — when `context.isImageLoaded`
137
+ * is `false`, emit a `console.warn` and resolve to `''` without
138
+ * touching the canvas.
139
+ * 2. **Discard ActiveSelection** — call
140
+ * `canvas.discardActiveObject` once before computing the export
141
+ * region. Subsequent steps render against the post-discard canvas
142
+ * state, which never carries a top-level `ActiveSelection`.
143
+ * 3. **Resolve format/quality**
144
+ * via {@link resolveExportFormat}.
145
+ * 4. **Resolve multiplier** — `options.multiplier || exportMultiplier || 1`.
146
+ * 5. **Compute region** — see {@link computeExportRegion}. Returns
147
+ * `null` for full-canvas exports and a floored {@link IntegerRegion}
148
+ * when `exportArea` is `'image'` and an `originalImage` is
149
+ * committed.
150
+ * 6. **Render** through {@link withMaskExportState} so mask styles are
151
+ * captured, the export bake-in (`opacity: 1, fill: '#000',
152
+ * strokeWidth: 0, stroke: null, selectable: false`) is applied for
153
+ * `mergeMask === true` exports, and the live styles are
154
+ * restored in a `finally` block whether the render resolved or
155
+ * threw. The inner step is a single
156
+ * `canvas.toDataURL` call — no intermediate `<canvas>`.
157
+ *
158
+ * @param context - Export context bundle.
159
+ * @param options - Optional {@link Base64ExportOptions}. Both `fileType`
160
+ * and `format` are accepted; when
161
+ * both are supplied, `fileType` wins.
162
+ * @returns Resolves to a `data:image/...;base64...` URL on
163
+ * success, or `''` when no image is loaded.
164
+ *
165
+ */
166
+ export declare function exportImageBase64(context: ExportServiceContext, options?: Base64ExportOptions): Promise<string>;
167
+ /**
168
+ * Render the live canvas to a `File`.
169
+ *
170
+ * The bytes come from {@link exportImageBase64} so format/quality/
171
+ * multiplier resolution stays consistent with the base64 path. The
172
+ * resulting data URL is repainted through an offscreen `<canvas>` only
173
+ * when its MIME prefix does not match the requested type — some browsers
174
+ * silently fall back to PNG when the requested format is unsupported,
175
+ * and the export contract requires the output MIME to match the resolved
176
+ * `fileType`.
177
+ *
178
+ * @param context - Export context bundle.
179
+ * @param options - Optional {@link ImageFileExportOptions}.
180
+ * @returns Resolves with the rendered `File`.
181
+ * @throws {@link ExportNotReadyError} when no image is loaded.
182
+ *
183
+ */
184
+ export declare function exportImageFile(context: ExportServiceContext, options?: ImageFileExportOptions): Promise<File>;
185
+ /**
186
+ * Trigger a browser download of the live canvas.
187
+ *
188
+ * Mirrors legacy's "anchor with `download` attribute" approach: an `<a>`
189
+ * element is created, pointed at the data URL, appended to the document
190
+ * so Firefox dispatches the click, clicked, and removed. The function
191
+ * returns synchronously; the data URL is rendered
192
+ * asynchronously and the click is deferred until that promise resolves.
193
+ *
194
+ * No-image gate emits the same `console.warn` as the
195
+ * other entry points and returns without touching the DOM.
196
+ *
197
+ * Errors raised by the underlying `exportImageBase64` call are reported
198
+ * with `console.error` rather than rethrown — `downloadImage` returns
199
+ * `void` and there is no caller-visible promise to reject.
200
+ *
201
+ * @param context - Export context bundle.
202
+ * @param fileName - Optional filename override. Defaults to
203
+ * `options.defaultDownloadFileName`.
204
+ *
205
+ */
206
+ export declare function downloadImage(context: ExportServiceContext, fileName?: string): void;
207
+ /**
208
+ * Dependency bundle passed by the `ImageEditor` facade into
209
+ * {@link mergeMasks}. Extends {@link ExportServiceContext} with the
210
+ * extra slots the merge pipeline needs:
211
+ *
212
+ * - the {@link HistoryManager} that records the merge as one undoable
213
+ * step;
214
+ * - the canonical `loadImage` entry point (transactional load with
215
+ * rollback) so a failed reload of the merged bitmap propagates back
216
+ * to the merge's own rollback path;
217
+ * - the `saveState` / `loadFromState` callbacks the orchestrator
218
+ * already wires for `undo` / `redo`, so the merge can capture and
219
+ * restore the pre-merge snapshot through the same
220
+ * `core/state-serializer.ts` helpers used by the rest of the editor;
221
+ * - a `removeAllMasks(saveHistory: false)` callback so the merge's
222
+ * single enclosing history entry is the only one pushed for the
223
+ * operation (exactly one history entry);
224
+ * - the live container element so the success path can preserve scroll
225
+ * even when the inner `loadImage` did not honor `preserveScroll`.
226
+ *
227
+ * Mirrors the shape of `image/image-loader.ts → LoadImageContext` for
228
+ * consistency across pipeline modules. The `ImageEditor` facade constructs
229
+ * this bundle from its own state.
230
+ *
231
+ */
232
+ export interface MergeMasksContext extends ExportServiceContext {
233
+ /** History manager that records the single merge command. */
234
+ readonly historyManager: HistoryManager;
235
+ /**
236
+ * Scrollable container wrapping the canvas, or `null`. Read at the
237
+ * head of `mergeMasks` so the success path can restore the captured
238
+ * scroll position regardless of the layout
239
+ * strategy applied by the inner `loadImage`.
240
+ */
241
+ readonly containerElement: HTMLElement | null;
242
+ /**
243
+ * Transactional image loader. The merge passes
244
+ * `{ preserveScroll: true}` so the inner load tries to keep scroll
245
+ * stable; the merge also restores scroll defensively at the tail of
246
+ * the success path.
247
+ */
248
+ loadImage(imageBase64: string, options?: LoadImageOptions): Promise<void>;
249
+ /**
250
+ * Capture a snapshot suitable for {@link loadFromStateFn}. Reads the
251
+ * orchestrator's `lastSnapshot`-producing path so the merge stores
252
+ * exactly the same wire format used by `undo` / `redo`.
253
+ */
254
+ saveState(): string;
255
+ /**
256
+ * Restore a snapshot produced by {@link saveStateFn}. Used both as
257
+ * the `undo` callback of the merge command and
258
+ * as the rollback step on any merge-pipeline failure.
259
+ */
260
+ loadFromState(snapshot: string): Promise<void>;
261
+ /**
262
+ * Remove every mask from the canvas WITHOUT pushing a history
263
+ * entry. The merge owns the single enclosing history entry, so the
264
+ * inner mask-removal step must opt out
265
+ * of its own history push.
266
+ */
267
+ removeAllMasksNoHistory(): void;
268
+ }
269
+ /**
270
+ * Flatten every mask into the base image and reload the flattened
271
+ * image as the new canvas state. Atomic with respect to the editor:
272
+ * either the merged image is committed and exactly one history entry
273
+ * is pushed, or the editor is rewound to its pre-merge state and the
274
+ * returned promise rejects with {@link MergeMasksError}.
275
+ *
276
+ * Steps, in order:
277
+ *
278
+ * 1. **No-op gates** — return without mutating anything when no image
279
+ * is loaded or when the canvas carries no mask objects (matches
280
+ * legacy's `if (!this.originalImage) return; … if (!masks.length) return;`).
281
+ * 2. **Capture pre-merge snapshot** — call
282
+ * `context.saveState` so the snapshot is suitable for
283
+ * `context.loadFromState(...)`. The snapshot is the one source of
284
+ * truth for both the merge command's `undo` and
285
+ * the rollback path.
286
+ * 3. **Discard ActiveSelection** — drop any active
287
+ * selection wrapper before computing the merged bitmap.
288
+ * 4. **Capture container scroll** — read `scrollTop` / `scrollLeft`
289
+ * from the editor container so the success path can restore them
290
+ * after the inner `loadImage` runs.
291
+ * 5. **Render the merged bitmap** — delegate to
292
+ * {@link exportImageBase64} with `exportArea: 'image'` and
293
+ * `multiplier: options.exportMultiplier`. The bake-in/restore
294
+ * bracket inside `exportImageBase64` ensures every live mask style
295
+ * is captured before the export-only style is applied and restored
296
+ * on both success and failure.
297
+ * 6. **Remove all masks** without pushing history — the merge owns
298
+ * the single enclosing history entry, so the
299
+ * inner removal step providedOptions out of its own history push.
300
+ * 7. **Reload the merged image** through the transactional
301
+ * `image/image-loader.ts` with `preserveScroll: true`. A failed
302
+ * reload propagates here so the rollback path catches it.
303
+ * 8. **Capture post-merge snapshot** — call `context.saveState` again so
304
+ * the merge command's `execute` can replay the merged state on
305
+ * redo.
306
+ * 9. **Restore scroll defensively** — write the
307
+ * captured `scrollTop` / `scrollLeft` back to the container even
308
+ * though the inner `loadImage` was asked to preserve scroll, so
309
+ * the user's view does not jump regardless of the layout strategy
310
+ * chosen by the loader.
311
+ * 10. **Push exactly one history command** whose
312
+ * `undo` restores the pre-merge snapshot via `context.loadFromState`
313
+ * and whose `execute` re-applies the merged snapshot via
314
+ * `context.loadFromState`. The command is pushed via
315
+ * {@link HistoryManager.push} (NOT `execute`) because the merged
316
+ * state is already on the canvas — the first `redo` call should
317
+ * re-run the merged-state restore, but the initial commit should
318
+ * not double-render.
319
+ *
320
+ * On any failure between step 3 and step 10, the pre-merge snapshot
321
+ * captured in step 3 is restored via `context.loadFromState` and the
322
+ * promise rejects with {@link MergeMasksError} wrapping the original
323
+ * cause. A failure inside the rollback itself is
324
+ * logged via `console.warn` but does not mask the original error.
325
+ *
326
+ * @param context - Editor dependency bundle — see {@link MergeMasksContext}.
327
+ * @returns Resolves on success; rejects with
328
+ * {@link MergeMasksError} on any pipeline failure (after
329
+ * the pre-merge snapshot has been restored).
330
+ *
331
+ */
332
+ export declare function mergeMasks(context: MergeMasksContext): Promise<void>;
333
+ //# sourceMappingURL=export-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export-service.d.ts","sourceRoot":"","sources":["../../../src/export/export-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2FG;AAEH,OAAO,KAAK,KAAK,QAAQ,MAAM,QAAQ,CAAC;AAIxC,OAAO,KAAK,EACR,mBAAmB,EAEnB,YAAY,EACZ,sBAAsB,EACtB,gBAAgB,EAGhB,eAAe,EAClB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAW,KAAK,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAsC7E;;;;;;;;;;GAUG;AACH,MAAM,WAAW,oBAAoB;IACjC,4DAA4D;IAC5D,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC;IACjC;;iCAE6B;IAC7B,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAElC;;;;OAIG;IACH,aAAa,IAAI,OAAO,CAAC;IAEzB;;;;;;OAMG;IACH,gBAAgB,IAAI,QAAQ,CAAC,WAAW,GAAG,IAAI,CAAC;CACnD;AAsqBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAsB,iBAAiB,CACnC,OAAO,EAAE,oBAAoB,EAC7B,OAAO,CAAC,EAAE,mBAAmB,GAC9B,OAAO,CAAC,MAAM,CAAC,CAiDjB;AAID;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,eAAe,CACjC,OAAO,EAAE,oBAAoB,EAC7B,OAAO,CAAC,EAAE,sBAAsB,GACjC,OAAO,CAAC,IAAI,CAAC,CAsCf;AAID;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAmCpF;AAID;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,WAAW,iBAAkB,SAAQ,oBAAoB;IAC3D,6DAA6D;IAC7D,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC;;;;;OAKG;IACH,QAAQ,CAAC,gBAAgB,EAAE,WAAW,GAAG,IAAI,CAAC;IAE9C;;;;;OAKG;IACH,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1E;;;;OAIG;IACH,SAAS,IAAI,MAAM,CAAC;IAEpB;;;;OAIG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE/C;;;;;OAKG;IACH,uBAAuB,IAAI,IAAI,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyH1E"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Detects the Fabric.js v7 module from the constructor's
3
+ * first argument or from `globalThis.fabric`, and reports
4
+ * whether a usable module was found.
5
+ *
6
+ * Two delivery channels share the same source tree:
7
+ *
8
+ * 1. **ESM consumers** pass the imported Fabric module explicitly:
9
+ * `new ImageEditor(fabric, options)`. Detected by `Canvas` being a
10
+ * function on the first argument.
11
+ *
12
+ * 2. **UMD / CDN consumers** rely on `<script>` tags exposing
13
+ * `window.fabric`, and call `new ImageEditor(options)` with no module
14
+ * argument. The adapter reads `globalScope.fabric` and treats the
15
+ * first argument as options.
16
+ *
17
+ * If neither channel produces a Fabric module with a `Canvas` constructor,
18
+ * the adapter returns `{ fabric: null, isFabricLoaded: false}`. The caller
19
+ * (the {@link ImageEditor} constructor) is then expected to make `init`
20
+ * and `loadImage` no-ops that resolve to `undefined`.
21
+ *
22
+ * Wrapping policy: this adapter does NOT proxy or normalize Fabric APIs.
23
+ * Callers MUST only invoke Fabric v7-compatible APIs directly:
24
+ * - `FabricImage.fromURL(...)` returning a Promise
25
+ * - `canvas.loadFromJSON(...)` returning a Promise
26
+ * - `canvas.setDimensions({ width, height})`
27
+ * - `canvas.bringObjectToFront(object)` / `canvas.sendObjectToBack(object)`
28
+ * - `canvas.backgroundColor` as a plain property (no setter callback)
29
+ * - `object.animate(...)` returning `Animation[]` (wrap with a Promise)
30
+ * (Fabric v7 surface only.)
31
+ *
32
+ * @module
33
+ */
34
+ import type { FabricModule, ImageEditorOptions } from '../core/public-types.js';
35
+ /**
36
+ * Result of {@link detectFabric}. The caller should:
37
+ * - assign `fabric` and `isFabricLoaded` to private fields on the editor,
38
+ * - use `options` as the ImageEditorOptions partial to feed into
39
+ * `core/default-options.ts`.
40
+ *
41
+ * `isFabricLoaded === false` means no usable Fabric module was found and
42
+ * `fabric` is `null`. The constructor SHALL guard `init` and
43
+ * `loadImage` accordingly.
44
+ */
45
+ export interface FabricDetectionResult {
46
+ /** The detected Fabric module, or `null` when no module is available. */
47
+ fabric: FabricModule | null;
48
+ /** `true` iff `fabric` is non-null and `fabric.Canvas` is a function. */
49
+ isFabricLoaded: boolean;
50
+ /** The options partial extracted from the constructor arguments. */
51
+ options: ImageEditorOptions;
52
+ }
53
+ /**
54
+ * Detects whether the constructor's first argument is the Fabric module or
55
+ * an `ImageEditorOptions` object, and finds Fabric in the appropriate place.
56
+ *
57
+ * Behavior matrix:
58
+ *
59
+ * | First arg | Result |
60
+ * | ------------------------------------------------- | ------------------------------------------------------------ |
61
+ * | Has `Canvas` function property | `{ fabric: arg, isFabricLoaded: true, options: maybeOptions}` |
62
+ * | Lacks `Canvas`, `globalScope.fabric.Canvas` is callback | `{ fabric: globalScope.fabric, isFabricLoaded: true, options: arg}` |
63
+ * | Lacks `Canvas`, no usable global | `{ fabric: null, isFabricLoaded: false, options: arg}` + single `console.error` |
64
+ *
65
+ * `null` / `undefined` first argument is normalized to an empty options
66
+ * object before being returned.
67
+ *
68
+ * @param fabricOrOptions - Constructor's first argument: either a Fabric module or an options partial.
69
+ * @param maybeOptions - Constructor's second argument, only consulted in the explicit-module form.
70
+ * @param globalScope - Global scope to consult for the UMD fallback. Defaults to `globalThis`; a custom scope is accepted to keep the adapter unit-testable.
71
+ *
72
+ */
73
+ export declare function detectFabric(fabricOrOptions: FabricModule | ImageEditorOptions | null | undefined, maybeOptions: ImageEditorOptions | undefined, globalScope?: typeof globalThis): FabricDetectionResult;
74
+ //# sourceMappingURL=fabric-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fabric-adapter.d.ts","sourceRoot":"","sources":["../../../src/fabric/fabric-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAIhF;;;;;;;;;GASG;AACH,MAAM,WAAW,qBAAqB;IAClC,yEAAyE;IACzE,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAC5B,yEAAyE;IACzE,cAAc,EAAE,OAAO,CAAC;IACxB,oEAAoE;IACpE,OAAO,EAAE,kBAAkB,CAAC;CAC/B;AAkCD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,YAAY,CACxB,eAAe,EAAE,YAAY,GAAG,kBAAkB,GAAG,IAAI,GAAG,SAAS,EACrE,YAAY,EAAE,kBAAkB,GAAG,SAAS,EAC5C,WAAW,GAAE,OAAO,UAAuB,GAC5C,qBAAqB,CAyCvB"}
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Promise-shaped wrapper around Fabric.js v7's
3
+ * {@link FabricNS.FabricObject.animate} for the current transform
4
+ * pipeline. v7's `animate(props, providedOptions)` returns an
5
+ * `Animation[]`-like map of contexts (one per animated
6
+ * property) and signals completion through the `onComplete`
7
+ * callback rather than a Promise. To plug it into the
8
+ * `AnimationQueue`, we wrap
9
+ * the call into a single Promise that resolves only after
10
+ * every property animation has fired its callback.
11
+ *
12
+ * ## Owned contracts
13
+ *
14
+ * - Wrap only Fabric v7-compatible APIs. v7's
15
+ * `object.animate` returns `Animation[]` (a map of per-property contexts)
16
+ * and reports completion via `onComplete`; multi-property tweens fire
17
+ * the callback once per property. {@link animateProps} hides that shape
18
+ * behind a single Promise so callers do not encode v7-specific shapes.
19
+ * - `scaleImage(factor)` animates the original
20
+ * image over `options.animationDuration`. `scaleX` and `scaleY` are
21
+ * tweened together, so the resolution count is two — see
22
+ * {@link animateProps}.
23
+ * - `rotateImage(degrees)` animates `angle` over
24
+ * `options.animationDuration`. The single-property case still flows
25
+ * through the same wrapper and resolves on the first `onComplete`.
26
+ * - In-flight animation callbacks check
27
+ * `isDisposed` (via `OperationGuard.isDisposed()`)
28
+ * before touching the canvas. {@link animateProps} short-circuits the
29
+ * `onChange` invocation when the editor has been disposed and still
30
+ * settles its Promise so queued callers never hang
31
+ * (the `AnimationQueue` contract).
32
+ * - Rotation animations temporarily set the image
33
+ * origin to `'center'/'center'` so Fabric tweens around the visual
34
+ * centroid. If dispose interrupts the animation between begin and the
35
+ * post-animation origin restore in `image/transform-controller.ts`,
36
+ * {@link restoreOrigin} replays the origin restore on the original
37
+ * image so it is not left in the temporary center-origin state.
38
+ *
39
+ * The wrapper does not call `saveState`, mark the queue, or set
40
+ * `isAnimating` — those live in the orchestrator and {@link OperationGuard}.
41
+ * This file is intentionally
42
+ * tiny: it owns one Fabric v7 quirk (the `Animation[]` return shape) and
43
+ * one dispose-safety detail (origin restore on interrupt). Per the
44
+ * Module Responsibilities table, it is NOT re-exported from
45
+ * `src/index.ts`.
46
+ *
47
+ * @module
48
+ */
49
+ import type * as FabricNS from 'fabric';
50
+ import type { OperationGuard } from '../core/operation-guard.js';
51
+ /**
52
+ * Options accepted by {@link animateProps}.
53
+ *
54
+ * Mirrors the subset of Fabric's `AnimationOptions` that the current transform
55
+ * pipeline uses. Additional fields (easing, duration jitter, etc.) are
56
+ * intentionally omitted so the wrapper has a single observable shape per
57
+ */
58
+ export interface AnimateOptions {
59
+ /** Animation duration in milliseconds (matches `options.animationDuration`). */
60
+ duration: number;
61
+ /**
62
+ * Per-frame hook. Called on every Fabric animation tick while the
63
+ * editor is not disposed. Typically used to call
64
+ * `canvas.requestRenderAll`.
65
+ *
66
+ * The wrapper guards this call with {@link OperationGuard.isDisposed}
67
+ * so post-dispose ticks become no-ops.
68
+ */
69
+ onChange?: () => void;
70
+ }
71
+ /**
72
+ * Animate one or more numeric properties on a Fabric object and resolve
73
+ * a single Promise once **all** property animations have completed.
74
+ *
75
+ * In Fabric v7, `object.animate(props, providedOptions)` returns a per-property
76
+ * `Animation[]`-like map and signals completion via the `onComplete`
77
+ * callback. For multi-property tweens (e.g. `scale` requires both
78
+ * `scaleX` and `scaleY`), `onComplete` fires once per property — so we
79
+ * count completions before resolving.
80
+ *
81
+ * Dispose safety:
82
+ *
83
+ * - When the editor is disposed mid-animation, {@link AnimateOptions.onChange}
84
+ * becomes a no-op so canvas references that may already be torn down
85
+ * are not touched.
86
+ * - The Promise still settles (resolves) so the
87
+ * `AnimationQueue` can drain queued
88
+ * callers without hanging.
89
+ *
90
+ * Caller responsibilities:
91
+ *
92
+ * - The caller (transform controller) is responsible for the post-animation
93
+ * `object.set({...}); object.setCoords;` snap so the final value is exact even
94
+ * if Fabric rounds the last tick. The wrapper does not commit values.
95
+ * - The caller is responsible for `OperationGuard.runAnimation` bracketing
96
+ * so `isAnimating` is `false` before the returned Promise resolves.
97
+ *
98
+ * @typeParam T - Concrete Fabric object subtype (FabricImage, Rect, etc.).
99
+ * @param object - Fabric object to animate.
100
+ * @param props - Map of property names to target numeric values (e.g.
101
+ * `{ scaleX: 1.5, scaleY: 1.5}` or `{ angle: 90}`).
102
+ * @param options - Duration and per-tick hook.
103
+ * @param guard - Operation guard providing the `isDisposed` flag that
104
+ * inner callbacks consult before touching the canvas.
105
+ * @returns Resolves once every animated property has signalled
106
+ * completion. Rejects only if `object.animate` itself throws
107
+ * synchronously (an empty `props` map resolves immediately).
108
+ *
109
+ */
110
+ export declare function animateProps<T extends FabricNS.FabricObject>(object: T, props: Record<string, number>, options: AnimateOptions, guard: OperationGuard): Promise<void>;
111
+ /**
112
+ * Restore the `originX` / `originY` pair on a Fabric object after a
113
+ * rotation animation has been interrupted by dispose.
114
+ *
115
+ * `image/transform-controller.ts.rotateImage` temporarily sets the image
116
+ * origin to `'center'/'center'` so Fabric tweens the angle around the
117
+ * visual centroid (Fabric v7 defaults `originX`/`originY` to `'center'`,
118
+ * but the compatibility path uses `'left'/'top'` for placement math). The
119
+ * controller restores the original origin after the animation resolves.
120
+ * If dispose runs between begin and the post-animation restore, the
121
+ * controller's restore branch is skipped — leaving the image in the
122
+ * temporary center-origin state. This helper replays the restore so a
123
+ * post-dispose inspector (or a re-init that reuses the image reference)
124
+ * sees the documented top-left origin.
125
+ *
126
+ * The helper is intentionally side-effect-tolerant:
127
+ *
128
+ * - Errors are swallowed because the canvas may already have been
129
+ * disposed and `setCoords` could throw on a torn-down object. The
130
+ * point of the helper is best-effort cleanup, not a hard guarantee.
131
+ * - It does NOT request a render. The canvas is, by contract, on its way
132
+ * out (this is only called from the dispose path).
133
+ *
134
+ * @param object - Fabric object whose origin pair needs restoring.
135
+ * In practice this is the editor's `originalImage`.
136
+ * @param originX - Origin to restore on the X axis (typically `'left'`).
137
+ * @param originY - Origin to restore on the Y axis (typically `'top'`).
138
+ *
139
+ */
140
+ export declare function restoreOrigin(object: FabricNS.FabricObject, originX: FabricNS.TOriginX, originY: FabricNS.TOriginY): void;
141
+ //# sourceMappingURL=fabric-animation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fabric-animation.d.ts","sourceRoot":"","sources":["../../../src/fabric/fabric-animation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,OAAO,KAAK,KAAK,QAAQ,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAQjE;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC3B,gFAAgF;IAChF,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,QAAQ,CAAC,YAAY,EACxD,MAAM,EAAE,CAAC,EACT,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC7B,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,cAAc,GACtB,OAAO,CAAC,IAAI,CAAC,CAiFf;AAeD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,aAAa,CACzB,MAAM,EAAE,QAAQ,CAAC,YAAY,EAC7B,OAAO,EAAE,QAAQ,CAAC,QAAQ,EAC1B,OAAO,EAAE,QAAQ,CAAC,QAAQ,GAC3B,IAAI,CASN"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Re-export shim for the {@link Command} class.
3
+ *
4
+ * The class itself is defined alongside {@link HistoryManager} in
5
+ * `./history-manager.ts` so the history module can be loaded directly
6
+ * from source by property tests running under Node's type-stripping
7
+ * mode without needing to resolve a sibling `.js` specifier at runtime.
8
+ *
9
+ * Module-layout consumers — and the canonical Module Responsibilities
10
+ * table — continue to see `command.ts` as the named home of the
11
+ * `Command` primitive.
12
+ *
13
+ * @module
14
+ */
15
+ export { Command } from './history-manager.js';
16
+ //# sourceMappingURL=command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../../src/history/command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC"}