@bensitu/image-editor 1.5.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/README.md +370 -466
  2. package/dist/cjs/index.cjs +5422 -0
  3. package/dist/cjs/index.cjs.map +1 -0
  4. package/dist/esm/animation/animation-queue.js +67 -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 +322 -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 +129 -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 +251 -0
  17. package/dist/esm/core/state-serializer.js.map +1 -0
  18. package/dist/esm/crop/crop-controller.js +403 -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 +596 -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 +37 -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 +245 -0
  33. package/dist/esm/image/image-loader.js.map +1 -0
  34. package/dist/esm/image/image-resampler.js +55 -0
  35. package/dist/esm/image/image-resampler.js.map +1 -0
  36. package/dist/esm/image/layout-manager.js +224 -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 +1740 -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 +332 -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 +47 -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/ui/dom-bindings.js +60 -0
  53. package/dist/esm/ui/dom-bindings.js.map +1 -0
  54. package/dist/esm/ui/ui-state.js +25 -0
  55. package/dist/esm/ui/ui-state.js.map +1 -0
  56. package/dist/esm/ui/visibility-state.js +11 -0
  57. package/dist/esm/ui/visibility-state.js.map +1 -0
  58. package/dist/esm/utils/canvas-region.js +100 -0
  59. package/dist/esm/utils/canvas-region.js.map +1 -0
  60. package/dist/esm/utils/dom.js +6 -0
  61. package/dist/esm/utils/dom.js.map +1 -0
  62. package/dist/esm/utils/file.js +53 -0
  63. package/dist/esm/utils/file.js.map +1 -0
  64. package/dist/esm/utils/number.js +24 -0
  65. package/dist/esm/utils/number.js.map +1 -0
  66. package/dist/esm/utils/timeout.js +17 -0
  67. package/dist/esm/utils/timeout.js.map +1 -0
  68. package/dist/types/animation/animation-queue.d.ts +111 -0
  69. package/dist/types/animation/animation-queue.d.ts.map +1 -0
  70. package/dist/types/core/callback-reporter.d.ts +125 -0
  71. package/dist/types/core/callback-reporter.d.ts.map +1 -0
  72. package/dist/types/core/default-options.d.ts +56 -0
  73. package/dist/types/core/default-options.d.ts.map +1 -0
  74. package/dist/types/core/errors.d.ts +142 -0
  75. package/dist/types/core/errors.d.ts.map +1 -0
  76. package/dist/types/core/operation-guard.d.ts +192 -0
  77. package/dist/types/core/operation-guard.d.ts.map +1 -0
  78. package/dist/types/core/public-types.d.ts +678 -0
  79. package/dist/types/core/public-types.d.ts.map +1 -0
  80. package/dist/types/core/state-serializer.d.ts +301 -0
  81. package/dist/types/core/state-serializer.d.ts.map +1 -0
  82. package/dist/types/crop/crop-controller.d.ts +407 -0
  83. package/dist/types/crop/crop-controller.d.ts.map +1 -0
  84. package/dist/types/export/export-format.d.ts +136 -0
  85. package/dist/types/export/export-format.d.ts.map +1 -0
  86. package/dist/types/export/export-service.d.ts +333 -0
  87. package/dist/types/export/export-service.d.ts.map +1 -0
  88. package/dist/types/fabric/fabric-adapter.d.ts +74 -0
  89. package/dist/types/fabric/fabric-adapter.d.ts.map +1 -0
  90. package/dist/types/fabric/fabric-animation.d.ts +141 -0
  91. package/dist/types/fabric/fabric-animation.d.ts.map +1 -0
  92. package/dist/types/history/command.d.ts +16 -0
  93. package/dist/types/history/command.d.ts.map +1 -0
  94. package/dist/types/history/history-manager.d.ts +129 -0
  95. package/dist/types/history/history-manager.d.ts.map +1 -0
  96. package/dist/types/image/image-loader.d.ts +265 -0
  97. package/dist/types/image/image-loader.d.ts.map +1 -0
  98. package/dist/types/image/image-resampler.d.ts +139 -0
  99. package/dist/types/image/image-resampler.d.ts.map +1 -0
  100. package/dist/types/image/layout-manager.d.ts +255 -0
  101. package/dist/types/image/layout-manager.d.ts.map +1 -0
  102. package/dist/types/image/transform-controller.d.ts +287 -0
  103. package/dist/types/image/transform-controller.d.ts.map +1 -0
  104. package/dist/types/image-editor.d.ts +650 -0
  105. package/dist/types/image-editor.d.ts.map +1 -0
  106. package/dist/types/index.d.cts +31 -0
  107. package/dist/types/index.d.cts.map +1 -0
  108. package/dist/types/index.d.ts +31 -0
  109. package/dist/types/index.d.ts.map +1 -0
  110. package/dist/types/mask/mask-factory.d.ts +209 -0
  111. package/dist/types/mask/mask-factory.d.ts.map +1 -0
  112. package/dist/types/mask/mask-label-manager.d.ts +171 -0
  113. package/dist/types/mask/mask-label-manager.d.ts.map +1 -0
  114. package/dist/types/mask/mask-list.d.ts +144 -0
  115. package/dist/types/mask/mask-list.d.ts.map +1 -0
  116. package/dist/types/mask/mask-style.d.ts +338 -0
  117. package/dist/types/mask/mask-style.d.ts.map +1 -0
  118. package/dist/types/ui/dom-bindings.d.ts +103 -0
  119. package/dist/types/ui/dom-bindings.d.ts.map +1 -0
  120. package/dist/types/ui/ui-state.d.ts +112 -0
  121. package/dist/types/ui/ui-state.d.ts.map +1 -0
  122. package/dist/types/ui/visibility-state.d.ts +77 -0
  123. package/dist/types/ui/visibility-state.d.ts.map +1 -0
  124. package/dist/types/utils/canvas-region.d.ts +177 -0
  125. package/dist/types/utils/canvas-region.d.ts.map +1 -0
  126. package/dist/types/utils/dom.d.ts +26 -0
  127. package/dist/types/utils/dom.d.ts.map +1 -0
  128. package/dist/types/utils/file.d.ts +80 -0
  129. package/dist/types/utils/file.d.ts.map +1 -0
  130. package/dist/types/utils/number.d.ts +132 -0
  131. package/dist/types/utils/number.d.ts.map +1 -0
  132. package/dist/types/utils/timeout.d.ts +84 -0
  133. package/dist/types/utils/timeout.d.ts.map +1 -0
  134. package/dist/umd/image-editor.umd.js +2 -0
  135. package/dist/umd/image-editor.umd.js.map +1 -0
  136. package/package.json +72 -66
  137. package/dist/image-editor.cjs +0 -4185
  138. package/dist/image-editor.cjs.map +0 -7
  139. package/dist/image-editor.esm.js +0 -4154
  140. package/dist/image-editor.esm.js.map +0 -7
  141. package/dist/image-editor.esm.min.js +0 -9
  142. package/dist/image-editor.esm.min.js.map +0 -7
  143. package/dist/image-editor.esm.min.mjs +0 -9
  144. package/dist/image-editor.esm.min.mjs.map +0 -7
  145. package/dist/image-editor.esm.mjs +0 -4154
  146. package/dist/image-editor.esm.mjs.map +0 -7
  147. package/dist/image-editor.js +0 -4151
  148. package/dist/image-editor.js.map +0 -7
  149. package/dist/image-editor.min.js +0 -9
  150. package/dist/image-editor.min.js.map +0 -7
  151. package/image-editor.d.ts +0 -269
  152. package/src/browser.js +0 -11
  153. package/src/esm.js +0 -9
  154. package/src/image-editor.js +0 -4775
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Pure layout helpers and a small viewport cache used by the
3
+ * `image-loader` pipeline. The layout manager owns three concerns
4
+ * used by the image-load pipeline:
5
+ *
6
+ * 1. Selecting exactly one layout strategy per load using the
7
+ * precedence `fit > cover > expand`.
8
+ * 2. Computing canvas dimensions and image scale for the selected
9
+ * strategy.
10
+ * 3. Measuring the visible container viewport with a hidden-tab cache
11
+ * and a final fall-back to `options.canvasWidth/canvasHeight`.
12
+ *
13
+ * The module also exposes a single sizing primitive,
14
+ * `applyCanvasDimensions`, which is the only place in the editor
15
+ * that calls `Canvas.setDimensions`. It rounds to integer pixels
16
+ * and forces a synchronous reflow on the container so that auto
17
+ * scrollbars settle before the next paint.
18
+ *
19
+ * The layout manager intentionally
20
+ * does NOT mutate developer CSS:
21
+ * - it never touches `canvas.style` or `container.style`
22
+ * (`width`, `height`, `display`, `overflow`),
23
+ * - it reads `clientWidth` / `clientHeight` and compensates for
24
+ * pre-existing auto scrollbars without any `overflow` toggle.
25
+ *
26
+ * @module
27
+ */
28
+ import type * as FabricNS from 'fabric';
29
+ import type { ResolvedOptions } from '../core/public-types.js';
30
+ /**
31
+ * Discriminator for the active layout strategy. Exactly one value is
32
+ * returned by {@link selectLayoutStrategy} per `loadImage` call.
33
+ */
34
+ export type LayoutStrategy = 'fit' | 'cover' | 'expand';
35
+ /**
36
+ * Subset of {@link ResolvedOptions} consumed by strategy selection.
37
+ *
38
+ * The accepted boolean flags in precedence order are
39
+ * `fitImageToCanvas`, `coverImageToCanvas`, and `expandCanvasToImage`.
40
+ */
41
+ export type LayoutFlags = Pick<ResolvedOptions, 'fitImageToCanvas' | 'coverImageToCanvas' | 'expandCanvasToImage'>;
42
+ /**
43
+ * Choose the active layout strategy using the documented precedence
44
+ * `fit > cover > expand`.
45
+ *
46
+ * The selection is a pure function of the boolean flags — it is
47
+ * independent of the order in which the option keys were declared,
48
+ * of any prior load, and of any prior canvas state.
49
+ *
50
+ * When all three flags are `false` the function falls back to
51
+ * `'expand'`. This matches the default-options resolution which sets
52
+ * `expandCanvasToImage: true` by default, and gives a deterministic
53
+ * answer even if a consumer disables every layout flag.
54
+ *
55
+ */
56
+ export declare function selectLayoutStrategy(options: LayoutFlags): LayoutStrategy;
57
+ /**
58
+ * Inspect the layout flags and report whether `fitImageToCanvas` and
59
+ * `coverImageToCanvas` are both enabled — the only pairing that is a
60
+ * real conflict because both rescale the image to the canvas viewport
61
+ * but with different aspect-ratio strategies. `expandCanvasToImage`
62
+ * defaults to `true`, so combining it with one of the other two is
63
+ * normal usage (the user providedOptions into a per-load override) and is not
64
+ * flagged.
65
+ *
66
+ * The selected strategy still follows the precedence in
67
+ * {@link selectLayoutStrategy}; this helper exists so the facade can
68
+ * emit a single warning through the documented reporting path when a
69
+ * real conflict is detected.
70
+ *
71
+ * `null` is returned when no conflict is present.
72
+ */
73
+ export interface LayoutConflict {
74
+ /** Strategies the caller had enabled simultaneously. */
75
+ readonly enabled: readonly LayoutStrategy[];
76
+ /** The strategy actually selected by `selectLayoutStrategy`. */
77
+ readonly selected: LayoutStrategy;
78
+ /** Human-readable summary suitable for `onWarning` consumers. */
79
+ readonly message: string;
80
+ }
81
+ export declare function detectLayoutConflict(options: LayoutFlags): LayoutConflict | null;
82
+ /**
83
+ * Two-axis viewport size in CSS pixels.
84
+ *
85
+ * Both axes are non-negative integers. `0` is permitted in transient
86
+ * states (e.g. before the editor is attached) but {@link ViewportCache}
87
+ * treats any axis of `0` as "hidden" and falls back to a cached or
88
+ * default value.
89
+ */
90
+ export interface ViewportSize {
91
+ width: number;
92
+ height: number;
93
+ }
94
+ /** Native scrollbar gutter size in CSS pixels. */
95
+ export type ScrollbarSize = ViewportSize;
96
+ export type OverflowAxis = 'horizontal' | 'vertical';
97
+ /**
98
+ * Hidden-container viewport cache.
99
+ *
100
+ * The editor often runs inside a tab, modal, or accordion that starts
101
+ * collapsed. A naive `clientWidth` read in that state returns `0` and
102
+ * collapses the canvas. The cache remembers the last non-zero
103
+ * measurement and reuses it while the container reports a zero
104
+ * dimension on either axis. When no non-zero
105
+ * measurement has been observed yet, the caller-supplied fallback —
106
+ * normally `(options.canvasWidth, options.canvasHeight)` — is used
107
+ * instead. When the container becomes visible
108
+ * again, the next `measure` call updates the cache so subsequent
109
+ * sizing decisions reflect the new viewport.
110
+ *
111
+ * Measurements compensate for pre-existing auto scrollbars by adding
112
+ * the scrollbar gutter back to the visible client size. This mirrors
113
+ * v1.4.2's viewport recovery while still preserving v2's rule that
114
+ * layout code never mutates the container's `overflow` style.
115
+ *
116
+ */
117
+ export declare class ViewportCache {
118
+ private lastVisible;
119
+ /**
120
+ * Measure the visible viewport for `container`, caching the result
121
+ * when both axes are non-zero. When either axis is zero, return the
122
+ * cached value if one exists, otherwise the supplied `fallback`.
123
+ *
124
+ * `null` containers (no element attached) yield the fallback
125
+ * directly; the cache is left untouched.
126
+ *
127
+ * @param container - The scrollable wrapper around the canvas, or
128
+ * `null` if no container has been resolved.
129
+ * @param fallback - The size to use when no live measurement and no
130
+ * cached measurement is available. Callers should
131
+ * pass `(options.canvasWidth, options.canvasHeight)`.
132
+ */
133
+ measure(container: HTMLElement | null, fallback: ViewportSize, scrollbarSize?: Partial<ScrollbarSize> | null): ViewportSize;
134
+ /**
135
+ * Return the cached viewport size without re-measuring. Useful for
136
+ * tests and for diagnostic logging. `null` indicates no non-zero
137
+ * measurement has been observed yet.
138
+ */
139
+ peek(): ViewportSize | null;
140
+ /**
141
+ * Discard the cached measurement. Intended for `dispose` paths.
142
+ * Calling `measure` again after `clear` behaves as if the editor
143
+ * has just been instantiated.
144
+ */
145
+ clear(): void;
146
+ }
147
+ /**
148
+ * Result of a layout computation. The image-loader applies these
149
+ * values to the Fabric canvas and the image object after a successful
150
+ * decode and Fabric load.
151
+ *
152
+ * `imageScale` is the Fabric `scaleX/scaleY` value that produces the
153
+ * desired on-canvas size for the image. `baseImageScale` is the
154
+ * editor's anchor scale used when computing zoom factors — see
155
+ * `image/transform-controller.ts` for how it is consumed.
156
+ */
157
+ export interface LayoutResult {
158
+ canvasWidth: number;
159
+ canvasHeight: number;
160
+ imageScale: number;
161
+ imageLeft: number;
162
+ imageTop: number;
163
+ baseImageScale: number;
164
+ }
165
+ /**
166
+ * Measure the browser's native scrollbar gutter. Overlay-scrollbar
167
+ * environments legitimately return zero on one or both axes.
168
+ */
169
+ export declare function measureScrollbarSize(ownerDocument?: Document | null): ScrollbarSize;
170
+ /**
171
+ * Measure the full layout viewport represented by the canvas container.
172
+ *
173
+ * In `overflow: auto` containers, `clientWidth` / `clientHeight` can already
174
+ * be reduced by scrollbars left over from the previous canvas size. v1.4.2
175
+ * avoided using that reduced viewport by adding the gutter back before the
176
+ * next Cover/Fit calculation. v2 keeps the same recovery rule without
177
+ * mutating `style.overflow`.
178
+ */
179
+ export declare function measureContainerViewport(container: HTMLElement | null, fallback: ViewportSize, scrollbarSize?: Partial<ScrollbarSize> | null): ViewportSize;
180
+ /**
181
+ * Compute canvas dimensions for content that may overflow the visible
182
+ * viewport.
183
+ *
184
+ * An overflowing axis grows to the content size, while a non-overflowing
185
+ * axis uses the viewport space left after the perpendicular scrollbar
186
+ * gutter is accounted for. This keeps Cover/Fit from accidentally creating
187
+ * a second scrollbar solely because the first scrollbar reduced the cross
188
+ * axis client size.
189
+ */
190
+ export declare function computeScrollableCanvasSize(contentWidth: number, contentHeight: number, viewport: ViewportSize, scrollbarSize?: Partial<ScrollbarSize> | null): ViewportSize;
191
+ /**
192
+ * Compute layout for the `fit` strategy.
193
+ *
194
+ * The canvas is set to the visible container viewport, falling back to
195
+ * `(options.canvasWidth/Height)` only when no viewport measurement is
196
+ * available, minus one pixel per axis to leave room for any sub-pixel
197
+ * rounding error and avoid tripping the container's auto scrollbars.
198
+ * The image is uniformly scaled down to fit, but never up
199
+ * (`Math.min(..., 1)`).
200
+ *
201
+ */
202
+ export declare function computeFitLayout(imageWidth: number, imageHeight: number, optionsCanvasWidth: number, optionsCanvasHeight: number, containerSize: ViewportSize): LayoutResult;
203
+ /**
204
+ * Compute layout for the `cover` strategy.
205
+ *
206
+ * The visible viewport determines the cover target (with a final fall-back
207
+ * to the configured canvas dimensions if a viewport axis is zero — the
208
+ * {@link ViewportCache} normally prevents this from happening). Large
209
+ * images are scaled down until Cover fills one axis without upscaling small
210
+ * images. When the filled axis would need a scrollbar, the scale is
211
+ * recomputed against the cross-axis space left after that scrollbar appears;
212
+ * this preserves the Cover invariant that at least one axis stays scroll-free.
213
+ *
214
+ */
215
+ export declare function computeCoverLayout(imageWidth: number, imageHeight: number, optionsCanvasWidth: number, optionsCanvasHeight: number, containerSize: ViewportSize, scrollbarSize?: Partial<ScrollbarSize> | null): LayoutResult;
216
+ /**
217
+ * Compute layout for the `expand` strategy.
218
+ *
219
+ * The canvas grows per-axis to `max(viewport, image)` and the image is
220
+ * placed at `(0, 0)` at its native size. `baseImageScale` is `1` to
221
+ * preserve the image's natural aspect ratio and top-left placement.
222
+ *
223
+ */
224
+ export declare function computeExpandLayout(imageWidth: number, imageHeight: number, optionsCanvasWidth: number, optionsCanvasHeight: number, containerSize: ViewportSize): LayoutResult;
225
+ /**
226
+ * Apply canvas pixel dimensions atomically and force a synchronous
227
+ * reflow on the container.
228
+ *
229
+ * In Fabric.js v7 the canvas is a pair of `<canvas>` elements
230
+ * (`lowerCanvasEl` for rendering, `upperCanvasEl` for pointer events).
231
+ * `Canvas.setDimensions` is the only API that updates both layers
232
+ * atomically and keeps their CSS in sync. Manually mutating
233
+ * Direct canvas element style writes only resize the lower layer and
234
+ * misaligns the upper layer's hit-test regions, so the editor always
235
+ * routes through this helper instead of touching the canvas element
236
+ * styles.
237
+ *
238
+ * After `setDimensions`, the container is reflowed by reading
239
+ * `offsetWidth` (see `utils/dom.ts → forceReflow`). This makes
240
+ * `overflow: auto` containers show or hide their scrollbars before
241
+ * the next paint instead of waiting for the following frame.
242
+ *
243
+ * The `width` and `height` arguments are clamped to a minimum of `1`
244
+ * and rounded to integer pixels. Non-finite or non-numeric inputs
245
+ * collapse to `1` rather than crashing the editor.
246
+ *
247
+ * @param canvas - The Fabric canvas to resize. Required.
248
+ * @param width - Target pixel width. Clamped to `>= 1` and rounded.
249
+ * @param height - Target pixel height. Clamped to `>= 1` and rounded.
250
+ * @param containerElement - The wrapper element to reflow. May be `null`
251
+ * when no container has been resolved; in that
252
+ * case the reflow is skipped without error.
253
+ */
254
+ export declare function applyCanvasDimensions(canvas: FabricNS.Canvas, width: number, height: number, containerElement: HTMLElement | null): void;
255
+ //# sourceMappingURL=layout-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout-manager.d.ts","sourceRoot":"","sources":["../../../src/image/layout-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,287 @@
1
+ /**
2
+ * Animated scale, rotate, and reset operations on the
3
+ * `originalImage` with history integration. Owns the current
4
+ * transform pipeline that the `ImageEditor` facade routes
5
+ * through the `AnimationQueue`.
6
+ *
7
+ * ## Owned contracts
8
+ *
9
+ * - `scaleImage(factor)` clamps `factor` to
10
+ * `[options.minScale, options.maxScale]` and animates the image scale
11
+ * to the clamped factor over `options.animationDuration` milliseconds.
12
+ * `scaleX` and `scaleY` are tweened together; {@link animateProps}
13
+ * hides the v7 per-property
14
+ * `onComplete` shape.
15
+ * - `rotateImage(degrees)` animates `angle` to
16
+ * the requested value over `options.animationDuration` milliseconds.
17
+ * The animation tweens around the visual centroid by temporarily
18
+ * switching the image origin to `'center'/'center'`; the original
19
+ * `'left'/'top'` origin is restored after the animation settles.
20
+ * - `scaleImage()` and `rotateImage()` return resolved promises for
21
+ * non-finite inputs without modifying canvas state. The guards are the
22
+ * first checks so no rollback bundle is needed.
23
+ * - `resetImageTransform` records exactly
24
+ * one history entry covering the full reset. Per-operation
25
+ * `saveCanvasState` calls inside the chained `scaleImage(1)` and
26
+ * `rotateImage(0)` are suppressed via
27
+ * {@link TransformContext.setSuppressSaveState}, and a single
28
+ * `saveCanvasState` is emitted at the very end (success path) so
29
+ * the entire reset is one undoable step. Failures inside the chain
30
+ * still release the suppression flag in `finally` so subsequent
31
+ * transforms continue to record history.
32
+ * - `scaleImage`, `rotateImage`, and
33
+ * `resetImageTransform` each call `saveCanvasState` on success so
34
+ * the new state is undoable. The orchestrator wires
35
+ * `saveCanvasState` to the full `core/state-serializer.ts → saveState`
36
+ * path; this module does not serialize the canvas itself.
37
+ *
38
+ * ## Dispose, animation guard, and origin safety
39
+ *
40
+ * The transform pipeline cooperates with three guards:
41
+ *
42
+ * 1. The orchestrator's {@link TransformContext.guard} sets
43
+ * `isAnimating` true/false around the Fabric animation. The
44
+ * `OperationGuard.runAnimation()` bracket clears the flag inside a
45
+ * `finally` so the public promise sees `isAnimating === false` before
46
+ * settling.
47
+ * 2. The animation queue (owned by the orchestrator) serializes
48
+ * `scaleImage`, `rotateImage`, and `resetImageTransform` so concurrent
49
+ * callers do not interleave mid-animation Fabric mutations. The
50
+ * transform controller does NOT enqueue on the queue itself; the
51
+ * orchestrator wraps each call through `animQueue.add(...)` before
52
+ * invoking the controller method.
53
+ * 3. The dispose flag on {@link TransformContext.guard} lets animation
54
+ * callbacks consult `guard.isDisposed()` before touching the canvas.
55
+ * Rotation animations also restore the `'left'/'top'` origin via
56
+ * {@link restoreOrigin} when dispose interrupts the animation so a
57
+ * post-dispose inspector or a re-init that reuses the image reference
58
+ * still sees the documented origin.
59
+ *
60
+ * ## Why a class with a context bundle?
61
+ *
62
+ * The legacy monolithic `ImageEditor` owned all transform state. This module keeps that
63
+ * state on the facade so `currentScale`, `currentRotation`,
64
+ * `baseImageScale`, and `shouldSuppressSaveState` remain on a single owner
65
+ * (these are part of the snapshot wire format). The
66
+ * controller therefore reads and writes through the
67
+ * {@link TransformContext} accessor pairs rather than duplicating the
68
+ * fields. Mirrors the same pattern used by
69
+ * `LoadImageContext`.
70
+ *
71
+ * Owner module references (per the documented "Mapping Contracts to
72
+ * modules" table): this module is imported by `image-editor.ts`. It is
73
+ * intentionally NOT re-exported from `src/index.ts`.
74
+ *
75
+ * @module
76
+ */
77
+ import type * as FabricNS from 'fabric';
78
+ import type { ResolvedOptions } from '../core/public-types.js';
79
+ import type { OperationGuard } from '../core/operation-guard.js';
80
+ /**
81
+ * Dependency bundle passed by the `ImageEditor` facade into
82
+ * {@link TransformController}. Mirrors the
83
+ * `LoadImageContext` shape so each pipeline
84
+ * keeps the orchestrator as the single owner of editor state.
85
+ *
86
+ * The facade is responsible for:
87
+ *
88
+ * - constructing a single {@link OperationGuard} per editor and reusing it
89
+ * across pipelines so `isAnimating` and `isDisposed` live in one place,
90
+ * - routing each public transform method through the animation queue
91
+ * before calling into the controller,
92
+ * - wiring {@link TransformContext.saveCanvasState} to the shared
93
+ * `core/state-serializer.ts → saveState` path so the snapshot embeds
94
+ * `_editorState`,
95
+ * - honouring {@link TransformContext.setSuppressSaveState} by making
96
+ * `saveCanvasState` a no-op while the suppression flag is `true` —
97
+ * this is what lets `resetImageTransform` record exactly one history
98
+ * entry,
99
+ * - performing post-animation UI refresh (rotation/scale input boxes,
100
+ * undo/redo buttons, mask label sync) — those concerns belong on the
101
+ * facade, not in the controller, so per-step UI updates remain
102
+ * centralized.
103
+ *
104
+ */
105
+ export interface TransformContext {
106
+ /** The live Fabric canvas the original image lives on. */
107
+ canvas: FabricNS.Canvas;
108
+ /** Resolved editor options (animation duration, min/max scale). */
109
+ options: ResolvedOptions;
110
+ /**
111
+ * Shared operation guard. The controller calls
112
+ * {@link OperationGuard.runAnimation} to set `isAnimating` for the
113
+ * lifetime of each Fabric animation and to honour the dispose flag.
114
+ */
115
+ guard: OperationGuard;
116
+ /** Reads the previously-committed `originalImage`. */
117
+ getOriginalImage(): FabricNS.FabricImage | null;
118
+ /** Reads `currentScale`. */
119
+ getCurrentScale(): number;
120
+ /** Writes `currentScale`. */
121
+ setCurrentScale(n: number): void;
122
+ /** Reads `currentRotation` in degrees. */
123
+ getCurrentRotation(): number;
124
+ /** Writes `currentRotation` in degrees. */
125
+ setCurrentRotation(n: number): void;
126
+ /** Reads `baseImageScale` chosen at load time. */
127
+ getBaseImageScale(): number;
128
+ /**
129
+ * Persist a snapshot to the history stack. Wired by the orchestrator
130
+ * to `core/state-serializer.ts → saveState` plus the surrounding
131
+ * mask-label hide/restore bracket. While the suppression flag set by
132
+ * `setSuppressSaveState(true)` is active, the orchestrator
133
+ * MUST treat this as a no-op so `resetImageTransform` records exactly
134
+ * one history entry.
135
+ */
136
+ saveCanvasState(): void;
137
+ /**
138
+ * Toggle the suppression flag that turns {@link saveCanvasState} into
139
+ * a no-op. Used by {@link TransformController.resetImageTransform} to
140
+ * collapse the per-operation history entries from the chained
141
+ * `scaleImage(1)` and `rotateImage(0)` calls into a single reset
142
+ * entry. The orchestrator owns the flag itself; this method is the
143
+ * controller's only handle on it.
144
+ */
145
+ setSuppressSaveState(suppress: boolean): void;
146
+ /**
147
+ * Optional post-snap hook the orchestrator wires for legacy parity. Runs
148
+ * AFTER the controller commits the final value with `set` /
149
+ * `setCoords` and BEFORE `saveCanvasState`. Used to:
150
+ *
151
+ * - resize the canvas to image bounds when
152
+ * `options.expandCanvasToImage` is `true`,
153
+ * - re-align the image bounding box to the canvas top-left,
154
+ * - re-sync mask label positions for visible labels.
155
+ *
156
+ * The hook is invoked only on the success path (no dispose) and only
157
+ * when defined — controllers running outside the facade (in tests)
158
+ * may omit it. Errors thrown from the hook propagate to the caller's
159
+ * `try/catch`, which mirrors legacy behaviour where the post-snap UI
160
+ * helpers ran inline inside the transform method.
161
+ */
162
+ afterTransformSnap?(): void;
163
+ }
164
+ /**
165
+ * Owns the animated `scaleImage`, `rotateImage`, and
166
+ * `resetImageTransform` operations. Each method is invoked from a queue
167
+ * entry on the orchestrator's animation queue and returns a Promise that
168
+ * resolves once the Fabric animation has settled and `saveCanvasState`
169
+ * has been called (or the operation no-opped because of dispose, an
170
+ * already-running animation, or non-finite input).
171
+ *
172
+ * Lifetime is one-per-editor — a fresh controller is constructed inside
173
+ * the `ImageEditor` constructor and lives until `dispose` runs. The
174
+ * controller holds no mutable state of its own; the {@link TransformContext}
175
+ * is the single source of truth.
176
+ *
177
+ */
178
+ export declare class TransformController {
179
+ private readonly context;
180
+ /**
181
+ * @param context - Dependency bundle owned by the `ImageEditor` facade.
182
+ */
183
+ constructor(context: TransformContext);
184
+ /**
185
+ * Animate the image scale to `factor`, clamped to
186
+ * `[options.minScale, options.maxScale]`.
187
+ *
188
+ * Steps:
189
+ *
190
+ * 1. Bail (resolved) when no image is loaded, an animation is already
191
+ * in progress, or the editor has been disposed.
192
+ * 2. Clamp `factor` to `[minScale, maxScale]` and update
193
+ * `currentScale` so toolbar inputs reflect the requested value
194
+ * BEFORE the animation begins (matches legacy timing).
195
+ * 3. Re-anchor the image origin to its current visual top-left so
196
+ * `scaleX` / `scaleY` tweens scale around the upper-left corner
197
+ * rather than the Fabric default centre.
198
+ * 4. Run a `scaleX` + `scaleY` tween via
199
+ * {@link animateProps} inside an
200
+ * {@link OperationGuard.runAnimation} bracket.
201
+ * 5. After the animation settles, snap to the exact target via
202
+ * `set({ scaleX, scaleY})` + `setCoords` so floating-point
203
+ * drift on the last tick does not leak into history.
204
+ * 6. Run the optional {@link TransformContext.afterTransformSnap}
205
+ * hook for canvas resize / mask label sync.
206
+ * 7. Call {@link TransformContext.saveCanvasState} so the new state
207
+ * is undoable. When dispose ran during the
208
+ * animation, the controller exits without snapping or saving so
209
+ * no torn-down canvas reference is touched.
210
+ *
211
+ * @param factor - Requested scale factor (1 = base, may exceed bounds —
212
+ * the value is clamped before use).
213
+ * @returns A promise that resolves once the animation has settled and
214
+ * history has been recorded, or immediately when the call
215
+ * short-circuits due to one of the bail conditions.
216
+ *
217
+ */
218
+ scaleImage(factor: number): Promise<void>;
219
+ /**
220
+ * Animate the image rotation to `degrees`. Returns a resolved promise
221
+ * without modifying canvas state when `degrees` is not finite.
222
+ *
223
+ * Steps:
224
+ *
225
+ * 1. Bail (resolved) on non-finite input, missing image, in-flight animation,
226
+ * or disposed editor.
227
+ * 2. Update `currentRotation` BEFORE the animation begins so toolbar
228
+ * inputs reflect the requested value during the tween.
229
+ * 3. Switch the image origin to `'center'/'center'` around its
230
+ * visual centroid so Fabric tweens the angle around the centre
231
+ * of mass rather than the top-left corner.
232
+ * 4. Run an `angle` tween via {@link animateProps} inside an
233
+ * {@link OperationGuard.runAnimation} bracket.
234
+ * 5. After the animation settles, snap to the exact target via
235
+ * `set('angle', degrees)` + `setCoords`.
236
+ * 6. Restore the `'left'/'top'` origin around the new visual
237
+ * top-left corner so subsequent placements (mask creation, crop
238
+ * rectangle, etc.) read off the documented origin.
239
+ * 7. Run the optional {@link TransformContext.afterTransformSnap}
240
+ * hook for canvas resize and mask label sync.
241
+ * 8. Call {@link TransformContext.saveCanvasState}.
242
+ *
243
+ * If dispose runs during the animation, step 6's origin restore
244
+ * branch is skipped — leaving the image in the temporary
245
+ * centre-origin state. The controller invokes
246
+ * {@link restoreOrigin} from `finally` so a post-dispose inspector
247
+ * still sees the documented `'left'/'top'` origin. `restoreOrigin`
248
+ * is documented as silent
249
+ * best-effort cleanup so it cannot mask the original animation
250
+ * error.
251
+ *
252
+ * @param degrees - Target rotation angle in degrees. Non-finite values are no-ops.
253
+ * @returns A promise that resolves once the animation has settled and
254
+ * history has been recorded, or immediately when the call
255
+ * short-circuits due to one of the bail conditions.
256
+ *
257
+ */
258
+ rotateImage(degrees: number): Promise<void>;
259
+ /**
260
+ * Animate the image to `scale = 1` and `rotation = 0` and record
261
+ * exactly one history entry covering the whole reset.
262
+ *
263
+ * Implementation strategy: chain `scaleImage(1)` and `rotateImage(0)`
264
+ * but suppress their per-operation history entries via
265
+ * {@link TransformContext.setSuppressSaveState}. After both
266
+ * sub-animations settle (or one rejects), release the suppression
267
+ * flag and emit a single `saveCanvasState` so the entire reset is
268
+ * one undoable step.
269
+ *
270
+ * Failure handling:
271
+ *
272
+ * - If `scaleImage(1)` or `rotateImage(0)` throws, the suppression
273
+ * flag is still released in `finally` so subsequent transforms
274
+ * continue to record history.
275
+ * - The single `saveCanvasState` call only runs on the success path.
276
+ * A failed reset therefore does not push a half-applied snapshot
277
+ * to history (the partially-applied scale or rotation is still
278
+ * recoverable via subsequent successful transforms).
279
+ *
280
+ * @returns A promise that resolves once both sub-animations have
281
+ * settled and the single history entry has been recorded.
282
+ * Resolves immediately as a no-op when no image is loaded.
283
+ *
284
+ */
285
+ resetImageTransform(): Promise<void>;
286
+ }
287
+ //# sourceMappingURL=transform-controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transform-controller.d.ts","sourceRoot":"","sources":["../../../src/image/transform-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AAEH,OAAO,KAAK,KAAK,QAAQ,MAAM,QAAQ,CAAC;AAExC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAKjE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,WAAW,gBAAgB;IAC7B,0DAA0D;IAC1D,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC;IACxB,mEAAmE;IACnE,OAAO,EAAE,eAAe,CAAC;IACzB;;;;OAIG;IACH,KAAK,EAAE,cAAc,CAAC;IAEtB,sDAAsD;IACtD,gBAAgB,IAAI,QAAQ,CAAC,WAAW,GAAG,IAAI,CAAC;IAEhD,4BAA4B;IAC5B,eAAe,IAAI,MAAM,CAAC;IAC1B,6BAA6B;IAC7B,eAAe,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAEjC,0CAA0C;IAC1C,kBAAkB,IAAI,MAAM,CAAC;IAC7B,2CAA2C;IAC3C,kBAAkB,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAEpC,kDAAkD;IAClD,iBAAiB,IAAI,MAAM,CAAC;IAE5B;;;;;;;OAOG;IACH,eAAe,IAAI,IAAI,CAAC;IAExB;;;;;;;OAOG;IACH,oBAAoB,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAC;IAE9C;;;;;;;;;;;;;;;OAeG;IACH,kBAAkB,CAAC,IAAI,IAAI,CAAC;CAC/B;AAID;;;;;;;;;;;;;GAaG;AACH,qBAAa,mBAAmB;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;IAE3C;;OAEG;gBACS,OAAO,EAAE,gBAAgB;IAIrC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiCG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA6D/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAsCG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA0EjD;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACG,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;CAgB7C"}