@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
package/README.md CHANGED
@@ -1,27 +1,19 @@
1
- # ImageEditor
1
+ # @bensitu/image-editor
2
2
 
3
- [![npm](https://img.shields.io/npm/l/image-editor.svg)](https://github.com/bensitu/image-editor)
3
+ [![npm](https://img.shields.io/npm/l/@bensitu/image-editor.svg)](https://github.com/bensitu/image-editor)
4
4
  [![npm](https://img.shields.io/npm/v/@bensitu/image-editor.svg)](https://www.npmjs.com/package/@bensitu/image-editor)
5
5
  [![](https://data.jsdelivr.com/v1/package/npm/@bensitu/image-editor/badge)](https://www.jsdelivr.com/package/npm/@bensitu/image-editor)
6
6
 
7
- A lightweight JavaScript wrapper around fabric.js that provides image loading, scaling, rotation, cropping, mask management, history, and export helpers.
7
+ A lightweight, TypeScript-first canvas image editor built on top of
8
+ [Fabric.js](https://fabricjs.com/) v7. `ImageEditor` wraps a Fabric canvas
9
+ with image loading, scale and rotation, mask creation, crop, history
10
+ (undo/redo), and base64/file export — exposed as a single canonical class
11
+ with a stable public surface.
8
12
 
9
- ## Overview
10
-
11
- ImageEditor offers:
12
-
13
- - Image loading from base64 data URLs or file input
14
- - Zoom in/out and reset transform helpers
15
- - Rotation with custom degrees or step-based controls
16
- - Crop mode with optional mask preservation
17
- - Mask creation, selection, removal, and label support
18
- - Undo/redo history helpers
19
- - Merge, download, base64 export, and `File` export helpers
20
- - Optional DOM/UI binding for common editor controls
21
- - Large-image downsampling to reduce browser memory pressure
22
- - Centralized error and warning callbacks
23
-
24
- **Note:** This library requires **fabric.js v5.x** to be loaded before creating or initializing the editor.
13
+ > **v2.0.0 is a behavior-preserving migration.** The v1 deprecated method
14
+ > and property aliases have been removed in favor of the canonical names
15
+ > documented below. See [`CHANGELOG.md`](./CHANGELOG.md) for the complete
16
+ > rename map.
25
17
 
26
18
  ## Demo
27
19
 
@@ -29,69 +21,109 @@ ImageEditor offers:
29
21
 
30
22
  ## Features
31
23
 
32
- - **Fabric.js-powered canvas** - Built on top of fabric.js.
33
- - **Image scaling** - Configurable min/max limits with smooth animation.
34
- - **Image rotation** - Step control and animated transitions.
35
- - **Auto-resizing** - Optional canvas resizing to match the image, fit the viewport, or cover the viewport.
36
- - **Image crop** - Enter a temporary crop mode and apply or cancel the crop.
37
- - **Mask management** - Add, remove, remove all, drag, resize, and optionally rotate masks.
38
- - **Mask labels** - Auto-sync labels with mask movement and scaling.
39
- - **History** - Undo and redo supported editor state changes.
40
- - **Performance optimization** - Downsample large images on load.
41
- - **Export & download** - Export as base64 data URL, `File`, or direct download.
42
- - **DOM/UI binding** - Bind common buttons, inputs, and placeholders by element ID.
24
+ - TypeScript source with `.d.ts` declarations published alongside the runtime
25
+ - Single canonical class `ImageEditor` exported as both default and named
26
+ - Fabric.js v7 declared as a peer dependency (no bundled Fabric copy)
27
+ - Multi-format publish: ESM (`import`), CommonJS (`require`), UMD (`<script>`),
28
+ TypeScript declarations (`types`)
29
+ - Transactional `loadImage` with rollback on decode, Fabric, downsample, or
30
+ timeout failures
31
+ - Animation queue serializes `scaleImage`, `rotateImage`,
32
+ `resetImageTransform`, `undo`, and `redo` so concurrent clicks never
33
+ interleave
34
+ - Bounded history stack with idempotent dispose
35
+ - Crop session with mask preservation toggle and atomic apply/cancel
36
+ - Base64 and `File` exports with PNG/JPEG/WebP support, configurable
37
+ multiplier, and mask compositing
38
+
39
+ ## Requirements
40
+
41
+ - **Node.js**: `>= 20` for development / building from source
42
+ - **Fabric.js**: peer dependency `^7.0.0` (must be installed by the consumer)
43
+ - **Browsers**: modern evergreen (Chrome, Firefox, Safari, Edge). The library
44
+ uses ES2022 features and the Fabric v7 promise-based API.
45
+ - **TypeScript**: strict consumers that compile dependencies with
46
+ `skipLibCheck: false` should include the ES2022 library in `tsconfig.json`.
47
+ Fabric v7.4 declarations also reference `jsdom` types, so install
48
+ `@types/jsdom` when your project type-checks Fabric's declaration files.
43
49
 
44
50
  ## Installation
45
51
 
46
- ### npm / pnpm / yarn
47
-
48
52
  ```bash
49
- npm i @bensitu/image-editor fabric
53
+ npm install @bensitu/image-editor fabric
50
54
  # or
51
55
  pnpm add @bensitu/image-editor fabric
52
56
  # or
53
57
  yarn add @bensitu/image-editor fabric
54
58
  ```
55
59
 
56
- ### ESM / bundler usage
60
+ `fabric@^7.0.0` is a peer dependency: install it explicitly so the editor
61
+ resolves the exact version your application uses.
57
62
 
58
- ```javascript
59
- import ImageEditor, {
60
- ImageEditor as NamedImageEditor,
61
- } from "@bensitu/image-editor";
62
- ```
63
+ ## Module formats and entry points
63
64
 
64
- ### Browser global usage
65
+ The package ships a single public entry, resolved by tooling via the
66
+ `exports` map in `package.json`:
65
67
 
66
- Include fabric.js first, then ImageEditor:
68
+ | Consumer | Resolves to |
69
+ | ------------------------------------- | ------------------------------ |
70
+ | ESM (`import`) | `dist/esm/index.js` |
71
+ | CommonJS (`require`) | `dist/cjs/index.cjs` |
72
+ | TypeScript (`types`) | `dist/types/index.d.ts` |
73
+ | UMD (`<script>`, `unpkg`, `jsdelivr`) | `dist/umd/image-editor.umd.js` |
74
+ | `default` fallback | `dist/esm/index.js` |
67
75
 
68
- ```html
69
- <!-- Fabric.js (required) -->
70
- <script src="https://cdn.jsdelivr.net/npm/fabric@5.5.2/dist/fabric.min.js"></script>
76
+ The UMD bundle exposes a global named `ImageEditor` and treats `fabric` as an
77
+ external global named `fabric`.
71
78
 
72
- <!-- ImageEditor -->
73
- <script src="path/to/dist/image-editor.min.js"></script>
74
- <!-- or -->
75
- <script src="https://cdn.jsdelivr.net/npm/@bensitu/image-editor/dist/image-editor.min.js"></script>
76
- ```
79
+ ## Dual entry-point convention
80
+
81
+ `ImageEditor`'s constructor accepts the Fabric module either explicitly (ESM
82
+ consumers) or via `globalThis.fabric` (UMD consumers). The same source ships
83
+ in all four formats:
84
+
85
+ - **Explicit module form** (recommended for bundled apps): pass the Fabric
86
+ module as the first argument.
77
87
 
78
- Use `dist/image-editor.js` or `dist/image-editor.min.js` for browser global script usage. Use `dist/image-editor.esm.mjs` or `dist/image-editor.esm.min.mjs` for standards-compliant ESM imports. Matching `.js` ESM builds are also generated for browser and bundler compatibility.
88
+ ```ts
89
+ import * as fabric from 'fabric';
90
+ import { ImageEditor } from '@bensitu/image-editor';
79
91
 
80
- ## Quick Start
92
+ const editor = new ImageEditor(fabric, {
93
+ canvasWidth: 800,
94
+ canvasHeight: 600,
95
+ });
96
+ ```
81
97
 
82
- ### HTML Structure
98
+ - **Global form** (UMD `<script>` consumers): omit the first argument; the
99
+ constructor reads `globalThis.fabric`.
100
+
101
+ ```html
102
+ <script src="https://cdn.jsdelivr.net/npm/fabric@7/dist/index.min.js"></script>
103
+ <script src="https://cdn.jsdelivr.net/npm/@bensitu/image-editor/dist/umd/image-editor.umd.js"></script>
104
+ <script>
105
+ const editor = new ImageEditor({
106
+ canvasWidth: 800,
107
+ canvasHeight: 600,
108
+ });
109
+ </script>
110
+ ```
111
+
112
+ If neither form yields a usable Fabric module, the constructor logs a single
113
+ descriptive `console.error` and `init()` and `loadImage()` become no-ops that
114
+ resolve to `undefined`.
115
+
116
+ ## Quick start
117
+
118
+ ### HTML
83
119
 
84
120
  ```html
85
- <!-- Canvas -->
86
- <canvas id="fabricCanvas"></canvas>
121
+ <canvas id="canvas"></canvas>
87
122
 
88
- <!-- Optional Controls -->
89
123
  <button id="zoomInButton">Zoom In</button>
90
124
  <button id="zoomOutButton">Zoom Out</button>
91
-
92
125
  <button id="rotateLeftButton">Rotate Left</button>
93
126
  <input id="rotateLeftDegreesInput" type="number" value="90" />
94
-
95
127
  <button id="rotateRightButton">Rotate Right</button>
96
128
  <input id="rotateRightDegreesInput" type="number" value="90" />
97
129
 
@@ -103,471 +135,343 @@ Use `dist/image-editor.js` or `dist/image-editor.min.js` for browser global scri
103
135
  <button id="applyCropButton">Apply Crop</button>
104
136
  <button id="cancelCropButton">Cancel Crop</button>
105
137
 
138
+ <button id="mergeMasksButton">Merge</button>
139
+ <button id="downloadImageButton">Download</button>
106
140
  <button id="undoButton">Undo</button>
107
141
  <button id="redoButton">Redo</button>
108
-
109
- <button id="mergeMasksButton">Merge</button>
110
142
  <button id="resetImageTransformButton">Reset</button>
111
- <button id="downloadImageButton">Download</button>
112
143
 
113
144
  <input id="imageInput" type="file" accept="image/*" />
145
+ <ul id="maskList"></ul>
114
146
  ```
115
147
 
116
- ### JavaScript Implementation
148
+ ### TypeScript / ESM
117
149
 
118
- The constructor options are optional. For the smallest setup, create an editor instance and bind it to a canvas element.
150
+ ```ts
151
+ import * as fabric from 'fabric';
152
+ import { ImageEditor } from '@bensitu/image-editor';
153
+ import type { ImageEditorOptions, MaskConfig } from '@bensitu/image-editor';
119
154
 
120
- ```javascript
121
- const editor = new ImageEditor();
155
+ const editor = new ImageEditor(fabric, {
156
+ canvasWidth: 800,
157
+ canvasHeight: 600,
158
+ backgroundColor: '#ffffff',
159
+ } satisfies ImageEditorOptions);
122
160
 
123
161
  editor.init({
124
- canvas: "fabricCanvas"
162
+ canvas: 'canvas',
163
+ zoomInButton: 'zoomInButton',
164
+ zoomOutButton: 'zoomOutButton',
165
+ rotateLeftButton: 'rotateLeftButton',
166
+ rotateLeftDegreesInput: 'rotateLeftDegreesInput',
167
+ rotateRightButton: 'rotateRightButton',
168
+ rotateRightDegreesInput: 'rotateRightDegreesInput',
169
+ createMaskButton: 'createMaskButton',
170
+ removeSelectedMaskButton: 'removeSelectedMaskButton',
171
+ removeAllMasksButton: 'removeAllMasksButton',
172
+ enterCropModeButton: 'enterCropModeButton',
173
+ applyCropButton: 'applyCropButton',
174
+ cancelCropButton: 'cancelCropButton',
175
+ mergeMasksButton: 'mergeMasksButton',
176
+ downloadImageButton: 'downloadImageButton',
177
+ undoButton: 'undoButton',
178
+ redoButton: 'redoButton',
179
+ resetImageTransformButton: 'resetImageTransformButton',
180
+ imageInput: 'imageInput',
181
+ maskList: 'maskList',
125
182
  });
126
- ```
127
183
 
128
- `canvas` is the only required DOM binding when your canvas element does not use the default ID `fabricCanvas`. All other DOM bindings are optional.
184
+ // Load an image programmatically (base64 data URL).
185
+ await editor.loadImage('data:image/jpeg;base64,...');
129
186
 
130
- #### Optional Configuration
187
+ // Add a rectangular mask, then export the result as base64.
188
+ const mask: MaskConfig = { shape: 'rect', width: 120, height: 80, left: '25%', top: '25%' };
189
+ editor.createMask(mask);
131
190
 
132
- You can pass options to override the built-in defaults.
133
-
134
- ```javascript
135
- const editor = new ImageEditor({
136
- canvasWidth: 800,
137
- canvasHeight: 600,
138
- backgroundColor: "#ffffff",
139
- initialImageBase64: null
140
- });
191
+ const dataUrl = await editor.exportImageBase64({ fileType: 'png' });
141
192
  ```
142
193
 
143
- These are common optional overrides, not required settings.
144
-
145
- #### Demo-style Configuration
146
-
147
- The docs demo uses an explicit configuration so the demo behavior is predictable. These options are not required for normal usage.
148
-
149
- ```javascript
150
- const editor = new ImageEditor({
151
- // Layout mode. Enable only one layout mode at a time.
152
- expandCanvasToImage: false,
153
- fitImageToCanvas: true,
154
- coverImageToCanvas: false,
155
-
156
- // Image loading / performance.
157
- downsampleOnLoad: true,
158
- initialImageBase64: null,
194
+ ### CommonJS
159
195
 
160
- // Mask behavior.
161
- maskRotatable: true,
162
- maskLabelOnSelect: true,
163
- maskLabelOffset: 5,
196
+ ```js
197
+ const fabric = require('fabric');
198
+ const { ImageEditor } = require('@bensitu/image-editor');
164
199
 
165
- // UI behavior.
166
- backgroundColor: "transparent",
167
- showPlaceholder: true,
168
- animationDuration: 100,
169
-
170
- // Export behavior.
171
- exportImageAreaByDefault: true
172
- });
173
-
174
- editor.init({
175
- canvas: "fabricCanvas",
176
- imagePlaceholder: "imagePlaceholder",
177
- scalePercentageInput: "scalePercentageInput",
178
- rotateLeftButton: "rotateLeftButton",
179
- rotateRightButton: "rotateRightButton",
180
- rotateLeftDegreesInput: "rotateLeftDegreesInput",
181
- rotateRightDegreesInput: "rotateRightDegreesInput",
182
- removeSelectedMaskButton: "removeSelectedMaskButton",
183
- removeAllMasksButton: "removeAllMasksButton",
184
- mergeMasksButton: "mergeMasksButton",
185
- downloadImageButton: "downloadImageButton",
186
- maskList: "maskList",
187
- enterCropModeButton: "enterCropModeButton",
188
- applyCropButton: "applyCropButton",
189
- cancelCropButton: "cancelCropButton",
190
- resetImageTransformButton: "resetImageTransformButton"
191
- });
200
+ const editor = new ImageEditor(fabric, { canvasWidth: 800, canvasHeight: 600 });
192
201
  ```
193
202
 
203
+ In v2, `require('@bensitu/image-editor')` returns a namespace object with
204
+ `ImageEditor`, `default`, and `isMaskObject`; it does not return the
205
+ constructor directly.
194
206
 
195
- ### Loading an Image Manually
207
+ ## Public API
196
208
 
197
- ```javascript
198
- async function loadImageDataUrl(imageBase64) {
199
- try {
200
- await editor.loadImage(imageBase64);
201
- } catch (error) {
202
- console.error("Image could not be loaded:", error);
203
- }
204
- }
209
+ `ImageEditor` is the only public class. The package barrel re-exports it as
210
+ both the default export and a named export, alongside `isMaskObject` and the
211
+ documented public types. Internal helpers (animation queue, command, history
212
+ manager, controllers, services, managers, utility modules) are intentionally
213
+ not exported and may change without notice.
205
214
 
206
- loadImageDataUrl("data:image/jpeg;base64,...");
207
- ```
215
+ ### Constructor
208
216
 
209
- ## Configuration Options
210
-
211
- All options are optional. If an option is not provided, the editor uses the default value listed below.
212
-
213
- When creating the editor instance, pass an options object to override defaults.
214
-
215
- | Option | Default | Description |
216
- | ----------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
217
- | `canvasWidth` | `800` | Initial canvas width in pixels. |
218
- | `canvasHeight` | `600` | Initial canvas height in pixels. |
219
- | `backgroundColor` | `transparent` | Canvas background color. |
220
- | `animationDuration` | `300` | Animation duration for scale and rotation operations, in milliseconds. |
221
- | `minScale` | `0.1` | Minimum image scale factor. |
222
- | `maxScale` | `5.0` | Maximum image scale factor. |
223
- | `scaleStep` | `0.05` | Scale increment/decrement used by zoom controls. |
224
- | `rotationStep` | `90` | Default rotation step in degrees. |
225
- | `expandCanvasToImage` | `true` | Expand the canvas to the loaded image size. |
226
- | `fitImageToCanvas` | `false` | Fit the loaded image inside the visible canvas viewport. |
227
- | `coverImageToCanvas` | `false` | Scale the image to cover the visible canvas viewport, allowing overflow when needed. |
228
- | `downsampleOnLoad` | `true` | Downsample large images before rendering. |
229
- | `downsampleMaxWidth` | `4000` | Maximum source image width before downsampling. |
230
- | `downsampleMaxHeight` | `3000` | Maximum source image height before downsampling. |
231
- | `downsampleQuality` | `0.92` | JPEG/WebP quality used when downsampling. `0` is valid and is preserved. |
232
- | `preserveSourceFormat` | `true` | Preserve the source image format where possible during downsampling. |
233
- | `downsampleMimeType` | `null` | Optional output MIME type for downsampled images. Supported values include `jpeg`, `jpg`, `png`, `webp`, `image/jpeg`, `image/png`, and `image/webp`. |
234
- | `imageLoadTimeoutMs` | `30000` | Timeout for image decode/load operations. |
235
- | `exportMultiplier` | `1` | Default scale multiplier for export. |
236
- | `exportImageAreaByDefault` | `true` | Export only the image area by default instead of the full canvas. |
237
- | `defaultMaskWidth` | `50` | Default mask width in pixels. |
238
- | `defaultMaskHeight` | `80` | Default mask height in pixels. |
239
- | `maskRotatable` | `false` | Whether masks can be rotated. |
240
- | `maskLabelOnSelect` | `true` | Show the mask label when a mask is selected. |
241
- | `maskLabelOffset` | `3` | Offset for mask labels from the mask's top-left corner. |
242
- | `maskName` | `mask` | Prefix for mask names and labels. |
243
- | `groupSelection` | `false` | Whether Fabric can select multiple masks as an active selection. |
244
- | `label.getText` | `(mask) => mask.maskName` | Callback for custom label text. The second argument is the mask's stable zero-based creation index (`mask.maskId - 1`). |
245
- | `showPlaceholder` | `true` | Show a placeholder when no image is loaded. |
246
- | `initialImageBase64` | `null` | Base64 data URL to auto-load during initialization. |
247
- | `defaultDownloadFileName` | `edited_image.jpg` | Default file name for downloads. |
248
- | `crop.preserveMasksAfterCrop` | `false` | Keep masks that intersect the crop area, shifted into the cropped canvas. Merge masks first if they should be baked into the image pixels. |
249
- | `onImageLoaded` | `null` | Callback invoked after an image finishes loading. |
250
- | `onError` | `null` | Callback invoked for recoverable internal errors. |
251
- | `onWarning` | `null` | Callback invoked for recoverable internal warnings. |
252
-
253
- `expandCanvasToImage`, `fitImageToCanvas`, and `coverImageToCanvas` are mutually exclusive layout modes. If more than one is enabled, the editor reports a warning and uses the first active mode in its existing load order.
254
-
255
- ## DOM Binding Keys
256
-
257
- `init(idMap)` binds editor behavior to DOM elements by ID. All keys are optional.
258
-
259
- | Key | Description |
260
- | --------------------------- | -------------------------------------------------------------- |
261
- | `canvas` | Required canvas element ID. |
262
- | `canvasContainer` | Optional scrollable viewport/container element ID. |
263
- | `imagePlaceholder` | Optional placeholder element shown when no image is loaded. |
264
- | `scalePercentageInput` | Optional element used to display the current scale percentage. |
265
- | `rotateLeftDegreesInput` | Optional input used by the rotate-left button. |
266
- | `rotateRightDegreesInput` | Optional input used by the rotate-right button. |
267
- | `rotateLeftButton` | Rotate image left. |
268
- | `rotateRightButton` | Rotate image right. |
269
- | `createMaskButton` | Create a new mask. |
270
- | `removeSelectedMaskButton` | Remove the currently selected mask. |
271
- | `removeAllMasksButton` | Remove all masks. |
272
- | `mergeMasksButton` | Merge masks into the base image. |
273
- | `downloadImageButton` | Download the edited image. |
274
- | `maskList` | Optional mask list container. |
275
- | `zoomInButton` | Zoom in. |
276
- | `zoomOutButton` | Zoom out. |
277
- | `resetImageTransformButton` | Reset scale and rotation. |
278
- | `undoButton` | Undo the last state change. |
279
- | `redoButton` | Redo the next state change. |
280
- | `imageInput` | File input used to load images. |
281
- | `uploadArea` | Optional clickable upload area that triggers the file input. |
282
- | `enterCropModeButton` | Enter crop mode. |
283
- | `applyCropButton` | Apply the current crop rectangle. |
284
- | `cancelCropButton` | Cancel crop mode. |
285
-
286
- ### Legacy DOM Binding Keys
287
-
288
- The following DOM binding keys remain supported in `1.x` for compatibility, but are deprecated and will be removed in `v2.0.0`.
289
-
290
- | Deprecated key | Use instead |
291
- | -------------------- | --------------------------- |
292
- | `imgPlaceholder` | `imagePlaceholder` |
293
- | `scaleRate` | `scalePercentageInput` |
294
- | `rotationLeftInput` | `rotateLeftDegreesInput` |
295
- | `rotationRightInput` | `rotateRightDegreesInput` |
296
- | `rotateLeftBtn` | `rotateLeftButton` |
297
- | `rotateRightBtn` | `rotateRightButton` |
298
- | `addMaskBtn` | `createMaskButton` |
299
- | `removeMaskBtn` | `removeSelectedMaskButton` |
300
- | `removeAllMasksBtn` | `removeAllMasksButton` |
301
- | `mergeBtn` | `mergeMasksButton` |
302
- | `downloadBtn` | `downloadImageButton` |
303
- | `zoomInBtn` | `zoomInButton` |
304
- | `zoomOutBtn` | `zoomOutButton` |
305
- | `resetBtn` | `resetImageTransformButton` |
306
- | `undoBtn` | `undoButton` |
307
- | `redoBtn` | `redoButton` |
308
- | `cropBtn` | `enterCropModeButton` |
309
- | `applyCropBtn` | `applyCropButton` |
310
- | `cancelCropBtn` | `cancelCropButton` |
311
-
312
- ## API Methods
313
-
314
- | Method | Returns | Description |
315
- | --------------------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------- |
316
- | `init(idMap)` | `void` | Bind the editor to DOM elements. Pass IDs in an object. |
317
- | `loadImage(imageBase64, options)` | `Promise<void>` | Load an image from a base64 data URL. Resolves after the Fabric image is on the canvas. |
318
- | `isImageLoaded()` | `boolean` | Return whether a valid image is loaded on the canvas. |
319
- | `isBusy()` | `boolean` | Return whether the editor is loading, animating, cropping, or running another compound operation. |
320
- | `scaleImage(factor)` | `Promise<void>` | Scale the image to the given factor relative to the base scale. |
321
- | `rotateImage(degrees)` | `Promise<void>` | Rotate the image to the given angle in degrees. |
322
- | `resetImageTransform()` | `Promise<void>` | Reset scale to `1` and rotation to `0`. |
323
- | `undo()` | `Promise<void>` | Undo the last state change. Resolves after the canvas state is restored. |
324
- | `redo()` | `Promise<void>` | Redo the next state change. Resolves after the canvas state is restored. |
325
- | `createMask(config)` | `fabric.Object \| null` | Create a mask on the canvas. Config can include shape, width, height, color, opacity, position, style, and callbacks. |
326
- | `removeSelectedMask()` | `void` | Remove the currently selected mask or selected masks. |
327
- | `removeAllMasks(options)` | `void` | Remove all masks from the canvas. |
328
- | `enterCropMode()` | `void` | Create a resizable/movable selection rectangle on top of the image. |
329
- | `cancelCrop()` | `void` | Cancel crop mode and remove the temporary selection rectangle. |
330
- | `applyCrop()` | `Promise<void>` | Apply the current crop rectangle to the canvas. |
331
- | `mergeMasks()` | `Promise<void>` | Merge masks into the base image on the canvas. |
332
- | `downloadImage(fileName)` | `void` | Download the edited image as a file. |
333
- | `exportImageBase64(options)` | `Promise<string>` | Export an image data URL. `fileType` can be `jpeg`, `jpg`, `png`, `webp`, or a supported image MIME type. |
334
- | `exportImageFile(options)` | `Promise<File>` | Export the current canvas as a `File` object. |
335
- | `saveState()` | `void` | Save the current editor state to history. Usually called internally after supported edits. |
336
- | `loadFromState(serializedState)` | `Promise<void>` | Restore the editor from a serialized canvas/editor state. |
337
-
338
- Deprecated method aliases are still available for compatibility and will be removed in `v2.0.0`.
339
-
340
- | Deprecated method | Use instead |
341
- | ------------------------- | ---------------------------- |
342
- | `reset()` | `resetImageTransform()` |
343
- | `addMask(config)` | `createMask(config)` |
344
- | `merge()` | `mergeMasks()` |
345
- | `getImageBase64(options)` | `exportImageBase64(options)` |
346
-
347
- ## Mask Configuration
348
-
349
- `createMask(config)` accepts an optional configuration object.
350
-
351
- | Option | Description |
352
- | ------------------ | ------------------------------------------------------------------------------------------ |
353
- | `shape` | Mask shape. Built-in values include `rect`, `circle`, `ellipse`, and `polygon`. |
354
- | `width` / `height` | Mask size in pixels, percentage string, or resolver callback. |
355
- | `radius` | Circle radius in pixels, percentage string, or resolver callback. |
356
- | `rx` / `ry` | Ellipse radius or rectangle corner radius. |
357
- | `points` | Polygon points as `{ x, y }` objects or `[x, y]` tuples. |
358
- | `left` / `top` | Mask position in pixels, percentage string, or resolver callback. |
359
- | `angle` | Initial rotation angle in degrees. |
360
- | `color` | Fill color. |
361
- | `alpha` | Opacity from `0` to `1`. |
362
- | `selectable` | Whether the mask can be selected. |
363
- | `hasControls` | Whether Fabric transform controls are shown. |
364
- | `styles` | Additional Fabric style properties, such as `stroke`, `strokeWidth`, or `strokeDashArray`. |
365
- | `fabricGenerator` | Factory callback for creating a custom Fabric object. |
366
- | `onCreate` | Callback invoked after the mask is added to the canvas. |
367
-
368
- Example:
369
-
370
- ```javascript
371
- const mask = editor.createMask({
372
- shape: "rect",
373
- width: 120,
374
- height: 80,
375
- left: 20,
376
- top: 20,
377
- color: "rgba(0, 0, 0, 0.5)",
378
- alpha: 0.5,
379
- });
217
+ ```ts
218
+ new ImageEditor(fabric: FabricModule, options?: ImageEditorOptions)
219
+ new ImageEditor(options?: ImageEditorOptions) // UMD: reads globalThis.fabric
380
220
  ```
381
221
 
382
- ## Crop Behavior
383
-
384
- By default, applying crop removes unmerged masks.
385
-
386
- This is intentional: an unmerged mask is still an editable overlay object, not part of the image pixels. When `crop.preserveMasksAfterCrop` is `false`, applying crop discards unmerged masks instead of trying to keep or partially crop those overlay objects.
222
+ ### Lifecycle
387
223
 
388
- Choose the workflow based on the result you want:
224
+ | Method | Description |
225
+ | -------------- | ------------------------------------------------------------------------------------ |
226
+ | `init(idMap?)` | Bind the editor to DOM elements. Pass an `ElementIdMap`; any key may be omitted. |
227
+ | `dispose()` | Tear down the editor, drain DOM bindings, and dispose the Fabric canvas. Idempotent. |
389
228
 
390
- - **Crop first, then add masks** when masks should be placed only on the final cropped image.
391
- - **Merge masks before cropping** when masks should become part of the image pixels and appear in the cropped result.
392
- - **Enable `crop.preserveMasksAfterCrop`** when masks should remain editable after crop. Only masks that intersect the crop area are kept and shifted into the cropped canvas.
393
- - **Keep the default `crop.preserveMasksAfterCrop: false`** when unmerged masks should be discarded by crop.
229
+ ### Image loading
394
230
 
395
- ### Preserve editable masks after crop
231
+ | Method | Description |
232
+ | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
233
+ | `loadImage(base64, options?)` | Load an image from a `data:image/...` URL. Returns `Promise<void>`. Transactional: any failure restores the prior canvas, scroll, overflow, and snapshot state. |
234
+ | `isImageLoaded()` | Returns `true` if a valid image is currently loaded on the canvas. |
235
+ | `isBusy()` | Returns `true` while the editor is loading, animating, or in crop mode. |
236
+ | `setLayoutMode(mode)` | Select the layout strategy for future image loads. `mode` is `'fit'`, `'cover'`, or `'expand'`. |
396
237
 
397
- ```javascript
398
- const editor = new ImageEditor({
399
- crop: {
400
- preserveMasksAfterCrop: true,
401
- },
402
- });
403
- ```
238
+ `LoadImageOptions` currently includes `preserveScroll?: boolean` for
239
+ preserving the container's scroll position across both successful loads and
240
+ rollback paths.
404
241
 
405
- ### Bake masks into the image before crop
242
+ Use `setLayoutMode()` instead of mutating internal options when a UI lets users
243
+ choose how the next image should be placed:
406
244
 
407
- ```javascript
408
- try {
409
- await editor.mergeMasks();
410
- editor.enterCropMode();
411
- await editor.applyCrop();
412
- } catch (error) {
413
- console.error("Crop workflow failed:", error);
414
- }
245
+ ```ts
246
+ editor.setLayoutMode('fit');
247
+ editor.setLayoutMode('cover');
248
+ editor.setLayoutMode('expand');
415
249
  ```
416
250
 
417
- ## Export Examples
418
-
419
- ### Export as Base64
420
-
421
- ```javascript
422
- try {
423
- const dataUrl = await editor.exportImageBase64({
424
- fileType: "jpeg",
425
- quality: 0.92,
426
- multiplier: 1,
427
- });
428
-
429
- console.log(dataUrl);
430
- } catch (error) {
431
- console.error("Export failed:", error);
432
- }
251
+ File-input helpers accept JPG, PNG, WebP, GIF, and BMP files. GIF and BMP are
252
+ decoded as static raster input for canvas editing; GIF animation and BMP/GIF
253
+ source-format preservation are not retained. Export output remains controlled by
254
+ the JPEG, PNG, or WebP export options.
255
+
256
+ ### Transforms
257
+
258
+ | Method | Description |
259
+ | ----------------------- | --------------------------------------------------------------------------------------------------- |
260
+ | `scaleImage(factor)` | Scale to `factor` (clamped to `[minScale, maxScale]`). Non-finite values are no-ops. Animated. |
261
+ | `rotateImage(degrees)` | Rotate to `degrees`. Non-finite values resolve without changing canvas state. Animated. |
262
+ | `resetImageTransform()` | Animate to scale 1 and rotation 0. Records exactly one history entry covering the entire transform. |
263
+
264
+ ### Masks
265
+
266
+ | Method | Description |
267
+ | -------------------------- | ----------------------------------------------------------------------------- |
268
+ | `createMask(config?)` | The single mask-creation entry point. Returns the new `MaskObject` or `null`. |
269
+ | `removeSelectedMask()` | Remove the currently selected mask and push one history entry. |
270
+ | `removeAllMasks(options?)` | Remove every mask. `options.saveHistory` defaults to `true`. |
271
+
272
+ `MaskConfig` supports rect, circle, ellipse, polygon, and a custom
273
+ `fabricGenerator`. Falsy values in `styles` (`0`, `false`, `null`, `''`,
274
+ `NaN`) are applied verbatim.
275
+
276
+ ### Crop
277
+
278
+ | Method | Description |
279
+ | ----------------- | ------------------------------------------------------------------------------------ |
280
+ | `enterCropMode()` | Add an interactive crop rectangle on top of the image. |
281
+ | `applyCrop()` | Apply the current crop region. Atomic: failure rolls back to the pre-crop snapshot. |
282
+ | `cancelCrop()` | Cancel crop mode and restore the prior canvas state without pushing a history entry. |
283
+
284
+ ### Merge and export
285
+
286
+ | Method | Description |
287
+ | ----------------------------- | ---------------------------------------------------------------------------------------------- |
288
+ | `mergeMasks()` | Bake masks into the base image atomically. Returns `Promise<void>`. |
289
+ | `exportImageBase64(options?)` | Returns `Promise<string>` (data URL). Resolves to `''` with a warning when no image is loaded. |
290
+ | `exportImageFile(options?)` | Returns `Promise<File>`. Rejects when no image is loaded. |
291
+ | `downloadImage(fileName?)` | Triggers a browser download. No-op when no image is loaded. |
292
+
293
+ `Base64ExportOptions` and `ImageFileExportOptions` separate mask compositing
294
+ from export region selection:
295
+
296
+ | Option | Default | Description |
297
+ | ------------ | --------- | --------------------------------------------------------------------------- |
298
+ | `mergeMask` | `true` | Flatten masks into exported pixels. Mask labels are never exported. |
299
+ | `exportArea` | `'image'` | `'image'` clips to the image bounding box; `'canvas'` exports the canvas. |
300
+ | `fileType` | `'jpeg'` | `'png'`, `'jpeg'`, `'jpg'`, `'webp'`, or matching full MIME strings. |
301
+ | `format` | `'jpeg'` | Alias for `fileType` on `exportImageBase64`; `fileType` wins when both set. |
302
+ | `quality` | `0.92` | Lossy quality clamped to `[0, 1]`; ignored for PNG. |
303
+ | `multiplier` | `1` | Output resolution multiplier. |
304
+ | `fileName` | option | `ImageFileExportOptions` only. Defaults to `defaultDownloadFileName`. |
305
+
306
+ ```ts
307
+ await editor.exportImageBase64({ exportArea: 'image', mergeMask: true });
308
+ await editor.exportImageBase64({ exportArea: 'image', mergeMask: false });
309
+ await editor.exportImageBase64({ exportArea: 'canvas', mergeMask: true });
310
+ await editor.exportImageBase64({ exportArea: 'canvas', mergeMask: false });
433
311
  ```
434
312
 
435
- ### Export as File
436
-
437
- ```javascript
438
- try {
439
- const file = await editor.exportImageFile({
440
- fileName: "edited_image.jpg",
441
- fileType: "jpeg",
442
- quality: 0.92,
443
- mergeMask: true,
444
- });
445
-
446
- console.log(file);
447
- } catch (error) {
448
- console.error("File export failed:", error);
449
- }
450
- ```
451
-
452
- ## Error Handling
453
-
454
- Some ImageEditor API methods may throw synchronously or reject their returned Promise when the operation cannot be completed.
455
-
456
- Recommended usage:
313
+ ### State and history
314
+
315
+ | Method | Description |
316
+ | ------------------------- | ------------------------------------------------------------------------------------- |
317
+ | `saveState()` | Capture a snapshot of the canvas plus editor metadata into the history stack. |
318
+ | `loadFromState(snapshot)` | Restore canvas, masks, and editor metadata from a snapshot. Returns `Promise<void>`. |
319
+ | `undo()` | Undo the last state change. Routed through the animation queue. No-op while disposed. |
320
+ | `redo()` | Redo the next state change. Routed through the animation queue. No-op while disposed. |
321
+
322
+ ## Configuration options
323
+
324
+ Pass an `ImageEditorOptions` object as the second constructor argument
325
+ (or as the only argument when using the UMD global form). Unknown keys are
326
+ ignored; nested `label` and `crop` objects are deep-merged with the defaults.
327
+
328
+ | Option | Default | Description |
329
+ | ------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
330
+ | `canvasWidth` | `800` | Initial and hidden-container fallback canvas width in pixels. |
331
+ | `canvasHeight` | `600` | Initial and hidden-container fallback canvas height in pixels. |
332
+ | `backgroundColor` | `'transparent'` | Fabric canvas background color. |
333
+ | `animationDuration` | `300` | Duration of scale and rotate animations (ms). |
334
+ | `minScale` | `0.1` | Minimum scale factor. |
335
+ | `maxScale` | `5.0` | Maximum scale factor. |
336
+ | `scaleStep` | `0.05` | Scale delta per zoom step. |
337
+ | `rotationStep` | `90` | Rotation step in degrees. |
338
+ | `expandCanvasToImage` | `true` | Grow the canvas to fit the loaded image (lowest layout precedence). |
339
+ | `fitImageToCanvas` | `false` | Fit the image inside the visible workspace viewport (highest layout precedence). |
340
+ | `coverImageToCanvas` | `false` | Scale large images down to cover the visible workspace, cap at native size, and expand overflowing canvas axes so the container can scroll. |
341
+ | `downsampleOnLoad` | `true` | Downsample large images on load. |
342
+ | `downsampleMaxWidth` | `4000` | Max width before downsampling kicks in. |
343
+ | `downsampleMaxHeight` | `3000` | Max height before downsampling kicks in. |
344
+ | `downsampleQuality` | `0.92` | Lossy quality used when downsampling and exporting. |
345
+ | `preserveSourceFormat` | `true` | Preserve PNG/WebP MIME through downsampling unless `downsampleMimeType` is set. |
346
+ | `downsampleMimeType` | `null` | Explicit downsample MIME type. Overrides `preserveSourceFormat`. |
347
+ | `imageLoadTimeoutMs` | `30000` | Maximum duration for both decode and Fabric image creation during `loadImage`. |
348
+ | `exportMultiplier` | `1` | Output resolution multiplier. |
349
+ | `maxExportPixels` | `50000000` | Maximum output pixel count after applying the export multiplier. Invalid values fall back to this default. |
350
+ | `maxHistorySize` | `50` | Maximum undo-history entries. Snapshots may include full image data URLs, so large images can duplicate memory across history entries. Lower this for memory-constrained pages. |
351
+ | `exportAreaByDefault` | `'image'` | Default export region for `exportImageBase64`, `exportImageFile`, and `downloadImage`. |
352
+ | `mergeMaskByDefault` | `true` | Default mask compositing behavior for `exportImageBase64`, `exportImageFile`, and `downloadImage`. |
353
+ | `defaultMaskWidth` | `50` | Default mask width. |
354
+ | `defaultMaskHeight` | `80` | Default mask height. |
355
+ | `maskRotatable` | `false` | Allow masks to be rotated by the user. |
356
+ | `maskLabelOnSelect` | `true` | Show a label above a selected mask. |
357
+ | `maskLabelOffset` | `3` | Pixel offset of the label from the mask's top-left corner. |
358
+ | `maskName` | `'mask'` | Prefix used for auto-generated mask names. |
359
+ | `groupSelection` | `false` | Allow Fabric multi-object group selection on the canvas. |
360
+ | `showPlaceholder` | `true` | Show a placeholder element while no image is loaded. |
361
+ | `initialImageBase64` | `null` | Base64 data URL auto-loaded after construction. |
362
+ | `defaultDownloadFileName` | `'edited_image.jpg'` | Default file name used by `downloadImage()`. |
363
+ | `onImageLoadStart` | `null` | Called before a valid image load begins. |
364
+ | `onImageLoaded` | `null` | Called as `(info, context)` once after a successful `loadImage`. Extra arguments are ignored by existing zero-argument JavaScript handlers. |
365
+ | `onImageCleared` | `null` | Called when a committed image is replaced or cleared. |
366
+ | `onImageChanged` | `null` | Called with a safe editor state snapshot after visible editor state changes. |
367
+ | `onBusyChange` | `null` | Called only when the public busy state changes. |
368
+ | `onEditorDisposed` | `null` | Called once when `dispose()` performs teardown. |
369
+ | `onMasksChanged` | `null` | Called with a shallow copy of current mask objects after the mask collection changes. |
370
+ | `onSelectionChange` | `null` | Called with selected mask payload after mask selection changes. |
371
+ | `onError` | `null` | Called as `(error, message)` when the editor reports an error. |
372
+ | `onWarning` | `null` | Called as `(error, message)` when the editor reports a recoverable warning. |
373
+ | `label` | see source | `LabelConfig` for selected-mask labels (`getText`, `textOptions`, `create`). |
374
+ | `crop` | see source | `CropConfig` (`minWidth`, `minHeight`, `padding`, `hideMasksDuringCrop`, `preserveMasksAfterCrop`, `allowRotationOfCropRect`, `exportFileType`, `exportQuality`). `applyCrop()` preserves the current image format by default (`'source'`) and falls back to PNG when unknown. |
375
+
376
+ `crop.exportFileType` defaults to `'source'`. Supported explicit values are
377
+ `'png'`, `'jpeg'`, `'jpg'`, `'webp'`, and full image MIME strings. PNG is
378
+ lossless and ignores `crop.exportQuality`; JPEG/WebP use `crop.exportQuality`
379
+ when finite, otherwise `downsampleQuality`, otherwise `0.92`. Choose JPEG/WebP
380
+ only when smaller intermediate crop output is preferred.
381
+
382
+ ## Example workflow
383
+
384
+ 1. Construct `ImageEditor` with options and call `init(idMap)` to wire it up.
385
+ 2. Load an image via `loadImage(base64)` or the bound file input.
386
+ 3. Adjust with `scaleImage`, `rotateImage`, `resetImageTransform`, and the
387
+ crop session.
388
+ 4. Add `createMask` calls and inspect via the `maskList` element.
389
+ 5. Use `mergeMasks` to bake masks into the image, then
390
+ `exportImageBase64`, `exportImageFile`, or `downloadImage` to produce
391
+ the final output.
392
+ 6. Call `dispose()` when the editor is unmounted.
393
+
394
+ ## Building from source
457
395
 
458
- - Wrap initialization and manual API calls in `try...catch`.
459
- - Always `await` Promise-based methods such as `loadImage()`, `scaleImage()`, `rotateImage()`, `resetImageTransform()`, `mergeMasks()`, `applyCrop()`, `undo()`, `redo()`, `exportImageBase64()`, `exportImageFile()`, and `loadFromState()`.
460
- - Use `onError` and `onWarning` for centralized logging or UI notifications.
461
- - Do not rely only on `onError`; manual API calls should still handle thrown errors or rejected Promises at the call site.
462
- - Check `editor.isBusy()` before starting user-triggered operations when needed, especially before loading, cropping, rotating, scaling, merging, undoing, redoing, or exporting.
463
- - Show user-friendly messages in your application instead of exposing raw error text directly.
464
-
465
- Example:
466
-
467
- ```javascript
468
- const editor = new ImageEditor({
469
- onError: (error, message) => {
470
- console.error("[ImageEditor]", message, error);
471
- showEditorMessage("The image editor could not complete the operation.");
472
- },
473
-
474
- onWarning: (error, message) => {
475
- console.warn("[ImageEditor]", message, error);
476
- },
477
- });
478
-
479
- async function rotateSafely(degrees) {
480
- if (editor.isBusy()) {
481
- return;
482
- }
483
-
484
- try {
485
- await editor.rotateImage(degrees);
486
- } catch (error) {
487
- console.error("Rotate failed:", error);
488
- showEditorMessage("Rotation failed. Please try again.");
489
- }
490
- }
491
-
492
- function showEditorMessage(message) {
493
- // Replace this with your own toast, alert, or inline message UI.
494
- console.log(message);
495
- }
396
+ ```bash
397
+ npm install
398
+ npm run build
496
399
  ```
497
400
 
498
- ### Common Failure Cases
499
-
500
- Common failure cases include:
501
-
502
- - fabric.js is not loaded before creating or initializing the editor.
503
- - The configured canvas element cannot be found.
504
- - The image data URL is invalid, unsupported, too large, or times out while loading.
505
- - Another operation is already running, such as image loading, animation, crop, undo, redo, merge, or export.
506
- - The editor has been disposed before the operation completes.
507
- - The browser cannot create a canvas export because of platform, memory, or security restrictions.
508
- - A custom `fabricGenerator`, label callback, or external event handler throws an error.
401
+ `npm run build` runs `clean → build:esm → build:cjs → build:types →
402
+ build:umd` in order, emitting:
509
403
 
510
- ## Example Workflow
404
+ - `dist/esm/index.js` (and the rest of the decomposed source tree)
405
+ - `dist/cjs/index.cjs`
406
+ - `dist/types/index.d.ts`
407
+ - `dist/umd/image-editor.umd.js`
511
408
 
512
- The recommended order depends on whether masks should remain editable, be baked into the image, or be discarded.
409
+ `npm test` runs the Node-based unit and property tests under `tests/`.
513
410
 
514
- ### Common workflow: crop first, then add masks
515
-
516
- Use this flow when masks are only needed on the final cropped image.
517
-
518
- 1. **Load an image** - Via file input or base64 data URL.
519
- 2. **Adjust positioning** - Zoom in/out, rotate, or reset as needed.
520
- 3. **Crop** - Optionally crop the image area.
521
- 4. **Add masks** - Highlight or cover specific areas on the cropped image.
522
- 5. **Merge result** - Flatten masks into the base image when needed.
523
- 6. **Download / export** - Save the final image.
524
-
525
- ### Alternative workflow: add masks before crop
526
-
527
- If masks are added before crop, choose one of these behaviors before applying crop:
528
-
529
- - Call `mergeMasks()` before `applyCrop()` if masks should become part of the cropped image pixels.
530
- - Set `crop.preserveMasksAfterCrop: true` if masks should stay editable after crop.
531
- - Keep the default `crop.preserveMasksAfterCrop: false` if unmerged masks should be removed by crop.
532
-
533
- Example:
534
-
535
- ```javascript
536
- // Masks become part of the image pixels before crop.
537
- await editor.mergeMasks();
538
- editor.enterCropMode();
539
- await editor.applyCrop();
540
- ```
541
-
542
- ## Local Build
411
+ For the full local release gate, run:
543
412
 
544
413
  ```bash
414
+ npm run format:check
415
+ npm run lint
416
+ npm run typecheck
417
+ npm test
545
418
  npm run build
419
+ npm run package:check
420
+ npm audit --audit-level=high
421
+ npm pack --dry-run
546
422
  ```
547
423
 
548
- ## Tests
549
-
550
- ```bash
551
- npm test
552
- ```
424
+ `npm run ci` combines format, lint, typecheck, tests, build, and package
425
+ linting. The test suite also supports a clean checkout where `dist/` has not
426
+ been built yet; integration helpers use source modules until build artifacts
427
+ exist, while partial `dist/` trees still fail the artifact checks.
553
428
 
554
- ## Browser Support
429
+ ## Browser support
555
430
 
556
431
  - Chrome 100+
557
432
  - Firefox 100+
558
433
  - Safari 15+
559
434
  - Edge 100+
560
435
 
561
- The package build targets these modern browsers and uses modern DOM and JavaScript features.
562
-
563
- IE11 and old mobile Safari are not supported by the distributed build. If you need them, transpile the package and provide any required DOM or JavaScript polyfills in your application.
564
-
565
- ## Dependencies
566
-
567
- - **fabric.js v5.x** Must be loaded before ImageEditor.
436
+ The library uses modern DOM and ES2022 features (optional chaining, classes,
437
+ `async`/`await`, native promises). Older targets must be transpiled by the
438
+ consumer.
439
+
440
+ ## Type declarations
441
+
442
+ Public types are re-exported from the package root:
443
+
444
+ ```ts
445
+ import type {
446
+ ImageEditorOptions,
447
+ ResolvedOptions,
448
+ LabelConfig,
449
+ CropConfig,
450
+ LoadImageOptions,
451
+ RemoveAllMasksOptions,
452
+ MaskConfig,
453
+ MaskObject,
454
+ MaskNumericProp,
455
+ ResolvedMaskConfig,
456
+ ImageMimeType,
457
+ ImageFileType,
458
+ NormalizedImageFormat,
459
+ ExportArea,
460
+ CropExportFileType,
461
+ Base64ExportOptions,
462
+ ImageFileExportOptions,
463
+ ImageInfo,
464
+ ImageEditorState,
465
+ ImageEditorSelection,
466
+ ImageEditorCallbackContext,
467
+ ImageEditorOperation,
468
+ ElementIdMap,
469
+ FabricModule,
470
+ } from '@bensitu/image-editor';
471
+ ```
568
472
 
569
473
  ## License
570
474
 
571
- MIT © 2026 Ben Situ
475
+ MIT © Ben Situ.
572
476
 
573
- Fabric.js is licensed under its own MIT license.
477
+ Fabric.js is distributed under its own MIT license.