@bensitu/image-editor 1.5.0 → 1.5.2

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.
package/README.md CHANGED
@@ -19,9 +19,10 @@ ImageEditor offers:
19
19
  - Merge, download, base64 export, and `File` export helpers
20
20
  - Optional DOM/UI binding for common editor controls
21
21
  - Large-image downsampling to reduce browser memory pressure
22
+ - Configurable history bounds for large image sessions
22
23
  - Centralized error and warning callbacks
23
24
 
24
- **Note:** This library requires **fabric.js v5.x** to be loaded before creating or initializing the editor.
25
+ **Note:** This library uses **fabric.js v5.x**. Bundler and CommonJS entries load Fabric through the peer dependency. Browser global usage needs `window.fabric` available by the time `init()` runs; constructing the editor before Fabric is available is tolerated as long as Fabric is registered before initialization.
25
26
 
26
27
  ## Demo
27
28
 
@@ -61,6 +62,14 @@ import ImageEditor, {
61
62
  } from "@bensitu/image-editor";
62
63
  ```
63
64
 
65
+ ### CommonJS usage
66
+
67
+ ```javascript
68
+ const ImageEditor = require("@bensitu/image-editor");
69
+
70
+ const editor = new ImageEditor();
71
+ ```
72
+
64
73
  ### Browser global usage
65
74
 
66
75
  Include fabric.js first, then ImageEditor:
@@ -115,50 +124,89 @@ Use `dist/image-editor.js` or `dist/image-editor.min.js` for browser global scri
115
124
 
116
125
  ### JavaScript Implementation
117
126
 
127
+ The constructor options are optional. For the smallest setup, create an editor instance and bind it to a canvas element.
128
+
129
+ ```javascript
130
+ const editor = new ImageEditor();
131
+
132
+ editor.init({
133
+ canvas: "fabricCanvas"
134
+ });
135
+ ```
136
+
137
+ `canvas` is the only required DOM binding when your canvas element does not use the default ID `fabricCanvas`. All other DOM bindings are optional.
138
+
139
+ #### Optional Configuration
140
+
141
+ You can pass options to override the built-in defaults.
142
+
118
143
  ```javascript
119
- // Create instance
120
144
  const editor = new ImageEditor({
121
145
  canvasWidth: 800,
122
146
  canvasHeight: 600,
123
147
  backgroundColor: "#ffffff",
148
+ initialImageBase64: null
149
+ });
150
+ ```
151
+
152
+ These are common optional overrides, not required settings.
153
+
154
+ #### Demo-style Configuration
155
+
156
+ The docs demo uses an explicit configuration so the demo behavior is predictable. These options are not required for normal usage.
157
+
158
+ ```javascript
159
+ const editor = new ImageEditor({
160
+ // Layout mode. Enable only one layout mode at a time.
161
+ expandCanvasToImage: false,
162
+ fitImageToCanvas: true,
163
+ coverImageToCanvas: false,
164
+
165
+ // Image loading / performance.
166
+ downsampleOnLoad: true,
124
167
  initialImageBase64: null,
125
168
 
126
- onError: (error, message) => {
127
- console.error("[ImageEditor]", message, error);
128
- },
169
+ // Mask behavior.
170
+ maskRotatable: true,
171
+ maskLabelOnSelect: true,
172
+ maskLabelOffset: 5,
129
173
 
130
- onWarning: (error, message) => {
131
- console.warn("[ImageEditor]", message, error);
132
- },
174
+ // UI behavior.
175
+ backgroundColor: "transparent",
176
+ showPlaceholder: true,
177
+ animationDuration: 100,
178
+
179
+ // Export behavior.
180
+ exportImageAreaByDefault: true
133
181
  });
134
182
 
135
- try {
136
- editor.init({
137
- canvas: "fabricCanvas",
138
- zoomInButton: "zoomInButton",
139
- zoomOutButton: "zoomOutButton",
140
- rotateLeftButton: "rotateLeftButton",
141
- rotateLeftDegreesInput: "rotateLeftDegreesInput",
142
- rotateRightButton: "rotateRightButton",
143
- rotateRightDegreesInput: "rotateRightDegreesInput",
144
- createMaskButton: "createMaskButton",
145
- removeSelectedMaskButton: "removeSelectedMaskButton",
146
- removeAllMasksButton: "removeAllMasksButton",
147
- enterCropModeButton: "enterCropModeButton",
148
- applyCropButton: "applyCropButton",
149
- cancelCropButton: "cancelCropButton",
150
- undoButton: "undoButton",
151
- redoButton: "redoButton",
152
- mergeMasksButton: "mergeMasksButton",
153
- resetImageTransformButton: "resetImageTransformButton",
154
- downloadImageButton: "downloadImageButton",
155
- imageInput: "imageInput",
156
- });
157
- } catch (error) {
158
- console.error("Failed to initialize ImageEditor:", error);
159
- }
183
+ editor.init({
184
+ canvas: "fabricCanvas",
185
+ canvasContainer: null,
186
+ imagePlaceholder: "imagePlaceholder",
187
+ scalePercentageInput: "scalePercentageInput",
188
+ rotateLeftButton: "rotateLeftButton",
189
+ rotateRightButton: "rotateRightButton",
190
+ rotateLeftDegreesInput: "rotateLeftDegreesInput",
191
+ rotateRightDegreesInput: "rotateRightDegreesInput",
192
+ createMaskButton: null,
193
+ removeSelectedMaskButton: "removeSelectedMaskButton",
194
+ removeAllMasksButton: "removeAllMasksButton",
195
+ mergeMasksButton: "mergeMasksButton",
196
+ downloadImageButton: "downloadImageButton",
197
+ maskList: "maskList",
198
+ enterCropModeButton: "enterCropModeButton",
199
+ applyCropButton: "applyCropButton",
200
+ cancelCropButton: "cancelCropButton",
201
+ resetImageTransformButton: "resetImageTransformButton",
202
+ imageInput: null,
203
+ uploadArea: null
204
+ });
160
205
  ```
161
206
 
207
+ The demo binds `createMaskButton`, `imageInput`, and `uploadArea` itself, so it passes `null` for those built-in bindings. Regular integrations can omit those keys to use the default IDs, or pass `null` to disable any optional binding.
208
+
209
+
162
210
  ### Loading an Image Manually
163
211
 
164
212
  ```javascript
@@ -175,6 +223,8 @@ loadImageDataUrl("data:image/jpeg;base64,...");
175
223
 
176
224
  ## Configuration Options
177
225
 
226
+ All options are optional. If an option is not provided, the editor uses the default value listed below.
227
+
178
228
  When creating the editor instance, pass an options object to override defaults.
179
229
 
180
230
  | Option | Default | Description |
@@ -198,6 +248,8 @@ When creating the editor instance, pass an options object to override defaults.
198
248
  | `downsampleMimeType` | `null` | Optional output MIME type for downsampled images. Supported values include `jpeg`, `jpg`, `png`, `webp`, `image/jpeg`, `image/png`, and `image/webp`. |
199
249
  | `imageLoadTimeoutMs` | `30000` | Timeout for image decode/load operations. |
200
250
  | `exportMultiplier` | `1` | Default scale multiplier for export. |
251
+ | `maxExportPixels` | `50000000` | Maximum output pixel count allowed per export after applying the multiplier. |
252
+ | `maxHistorySize` | `50` | Maximum undo/redo history entries to retain. Large base64 source images can make each history snapshot expensive. |
201
253
  | `exportImageAreaByDefault` | `true` | Export only the image area by default instead of the full canvas. |
202
254
  | `defaultMaskWidth` | `50` | Default mask width in pixels. |
203
255
  | `defaultMaskHeight` | `80` | Default mask height in pixels. |
@@ -207,19 +259,28 @@ When creating the editor instance, pass an options object to override defaults.
207
259
  | `maskName` | `mask` | Prefix for mask names and labels. |
208
260
  | `groupSelection` | `false` | Whether Fabric can select multiple masks as an active selection. |
209
261
  | `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`). |
210
- | `showPlaceholder` | `true` | Show a placeholder when no image is loaded. |
262
+ | `label.create` | `undefined` | Optional callback that returns a custom Fabric label object for the selected mask. Invalid or throwing callbacks fall back to the default label. |
263
+ | `label.textOptions` | see defaults | Fabric text options merged into the default label when `label.create` is not used or falls back. |
264
+ | `showPlaceholder` | `true` | Show a placeholder when no image is loaded. When `false`, internal load, rollback, and status paths keep the placeholder hidden. |
211
265
  | `initialImageBase64` | `null` | Base64 data URL to auto-load during initialization. |
212
266
  | `defaultDownloadFileName` | `edited_image.jpg` | Default file name for downloads. |
267
+ | `crop.minWidth` | `100` | Minimum crop rectangle width, clamped to the current image bounds. |
268
+ | `crop.minHeight` | `100` | Minimum crop rectangle height, clamped to the current image bounds. |
269
+ | `crop.padding` | `10` | Initial inset from the image bounds when entering crop mode. |
270
+ | `crop.hideMasksDuringCrop` | `true` | Hide editable masks while crop mode is active. |
213
271
  | `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. |
214
- | `onImageLoaded` | `null` | Callback invoked after an image finishes loading. |
272
+ | `crop.allowRotationOfCropRect` | `false` | Reserved for future rotated crop support. In v1.x, crop rectangles stay axis-aligned; setting this to `true` reports a warning and rotation remains disabled. |
273
+ | `onImageLoaded` | `null` | Callback invoked after an image load is committed. Callback errors are reported as warnings and do not roll back the loaded image. |
215
274
  | `onError` | `null` | Callback invoked for recoverable internal errors. |
216
275
  | `onWarning` | `null` | Callback invoked for recoverable internal warnings. |
217
276
 
218
- `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.
277
+ `expandCanvasToImage`, `fitImageToCanvas`, and `coverImageToCanvas` are mutually exclusive layout modes. If more than one is enabled, the editor reports a warning and uses this precedence: `fitImageToCanvas`, then `coverImageToCanvas`, then `expandCanvasToImage`.
278
+
279
+ Numeric runtime options are normalized during construction. Non-finite or invalid dimensions, scale limits, export limits, crop sizes, label offsets, and history sizes fall back to safe defaults. `animationDuration: 0` and `downsampleQuality: 0` remain valid.
219
280
 
220
281
  ## DOM Binding Keys
221
282
 
222
- `init(idMap)` binds editor behavior to DOM elements by ID. All keys are optional.
283
+ `init(idMap)` binds editor behavior to DOM elements by ID. All keys are optional when the default IDs are present. Optional bindings can also be set to `null` to disable the built-in listener for that element.
223
284
 
224
285
  | Key | Description |
225
286
  | --------------------------- | -------------------------------------------------------------- |
@@ -279,11 +340,12 @@ The following DOM binding keys remain supported in `1.x` for compatibility, but
279
340
  | Method | Returns | Description |
280
341
  | --------------------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------- |
281
342
  | `init(idMap)` | `void` | Bind the editor to DOM elements. Pass IDs in an object. |
343
+ | `dispose()` | `void` | Cleans up the canvas, listeners, crop state, animations, and captured DOM visibility/disabled/pointer-events state. |
282
344
  | `loadImage(imageBase64, options)` | `Promise<void>` | Load an image from a base64 data URL. Resolves after the Fabric image is on the canvas. |
283
345
  | `isImageLoaded()` | `boolean` | Return whether a valid image is loaded on the canvas. |
284
- | `isBusy()` | `boolean` | Return whether the editor is loading, animating, cropping, or running another compound operation. |
285
- | `scaleImage(factor)` | `Promise<void>` | Scale the image to the given factor relative to the base scale. |
286
- | `rotateImage(degrees)` | `Promise<void>` | Rotate the image to the given angle in degrees. |
346
+ | `isBusy()` | `boolean` | Return whether the editor is loading, animating, cropping, applying crop, or running another compound operation. |
347
+ | `scaleImage(factor)` | `Promise<void>` | Scale the image to the given finite factor relative to the base scale. Non-finite values are ignored. |
348
+ | `rotateImage(degrees)` | `Promise<void>` | Rotate the image to the given finite angle in degrees. Non-finite values are ignored. |
287
349
  | `resetImageTransform()` | `Promise<void>` | Reset scale to `1` and rotation to `0`. |
288
350
  | `undo()` | `Promise<void>` | Undo the last state change. Resolves after the canvas state is restored. |
289
351
  | `redo()` | `Promise<void>` | Redo the next state change. Resolves after the canvas state is restored. |
@@ -291,8 +353,8 @@ The following DOM binding keys remain supported in `1.x` for compatibility, but
291
353
  | `removeSelectedMask()` | `void` | Remove the currently selected mask or selected masks. |
292
354
  | `removeAllMasks(options)` | `void` | Remove all masks from the canvas. |
293
355
  | `enterCropMode()` | `void` | Create a resizable/movable selection rectangle on top of the image. |
294
- | `cancelCrop()` | `void` | Cancel crop mode and remove the temporary selection rectangle. |
295
- | `applyCrop()` | `Promise<void>` | Apply the current crop rectangle to the canvas. |
356
+ | `cancelCrop()` | `void` | Cancel crop mode and remove the temporary selection rectangle. No-ops while `applyCrop()` is already running. |
357
+ | `applyCrop()` | `Promise<void>` | Apply the current valid crop rectangle to the canvas. Invalid crop regions warn and keep crop mode active. |
296
358
  | `mergeMasks()` | `Promise<void>` | Merge masks into the base image on the canvas. |
297
359
  | `downloadImage(fileName)` | `void` | Download the edited image as a file. |
298
360
  | `exportImageBase64(options)` | `Promise<string>` | Export an image data URL. `fileType` can be `jpeg`, `jpg`, `png`, `webp`, or a supported image MIME type. |
@@ -313,6 +375,8 @@ Deprecated method aliases are still available for compatibility and will be remo
313
375
 
314
376
  `createMask(config)` accepts an optional configuration object.
315
377
 
378
+ Invalid mask configuration, such as non-finite numeric values, non-positive dimensions/radii, duplicate or zero-area polygon points, malformed polygon points, throwing custom generators, or custom generators that do not return a Fabric object, is rejected with a warning and returns `null` without mutating the canvas or history.
379
+
316
380
  | Option | Description |
317
381
  | ------------------ | ------------------------------------------------------------------------------------------ |
318
382
  | `shape` | Mask shape. Built-in values include `rect`, `circle`, `ellipse`, and `polygon`. |
@@ -330,6 +394,10 @@ Deprecated method aliases are still available for compatibility and will be remo
330
394
  | `fabricGenerator` | Factory callback for creating a custom Fabric object. |
331
395
  | `onCreate` | Callback invoked after the mask is added to the canvas. |
332
396
 
397
+ `fabricGenerator` runs before the mask is committed. If it throws or returns an invalid object, `createMask()` reports a warning and returns `null`. `onCreate` runs after the mask and history entry are committed; if it throws, the mask remains on the canvas and the error is reported as a warning.
398
+
399
+ Label callbacks are also isolated. If `label.create` throws or returns an invalid object, the editor uses the default Fabric text label. If `label.getText` throws, the editor uses `mask.maskName`.
400
+
333
401
  Example:
334
402
 
335
403
  ```javascript
@@ -350,6 +418,10 @@ By default, applying crop removes unmerged masks.
350
418
 
351
419
  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.
352
420
 
421
+ `applyCrop()` owns a short crop-apply lock while it exports the selected region and reloads the cropped image. Calls to `cancelCrop()` or `enterCropMode()` during that pending apply are ignored and reported as warnings so crop state cannot be mutated mid-apply. The crop region is validated before export; invalid or out-of-bounds regions do not silently produce a 1x1 image.
422
+
423
+ While crop mode is active, the built-in DOM binding disables editor operation controls but keeps the canvas, canvas container, and placeholder interaction available so the crop rectangle and scrollable viewport remain usable. The apply/cancel crop buttons stay enabled.
424
+
353
425
  Choose the workflow based on the result you want:
354
426
 
355
427
  - **Crop first, then add masks** when masks should be placed only on the final cropped image.
@@ -414,6 +486,24 @@ try {
414
486
  }
415
487
  ```
416
488
 
489
+ Exports are limited by `maxExportPixels`. Increase that option only when the host page can tolerate the memory cost of larger canvas exports.
490
+
491
+ ```javascript
492
+ const editor = new ImageEditor({
493
+ maxExportPixels: 80000000,
494
+ });
495
+ ```
496
+
497
+ JPEG exports cannot preserve transparency. Transparent, zero-alpha, empty, or invalid `backgroundColor` values are flattened against white; valid opaque CSS colors are preserved.
498
+
499
+ Undo/redo history is limited by `maxHistorySize`. The default is `50`; lower it for workflows that load large base64 images and perform many edits.
500
+
501
+ ```javascript
502
+ const editor = new ImageEditor({
503
+ maxHistorySize: 20,
504
+ });
505
+ ```
506
+
417
507
  ## Error Handling
418
508
 
419
509
  Some ImageEditor API methods may throw synchronously or reject their returned Promise when the operation cannot be completed.
@@ -464,13 +554,13 @@ function showEditorMessage(message) {
464
554
 
465
555
  Common failure cases include:
466
556
 
467
- - fabric.js is not loaded before creating or initializing the editor.
557
+ - fabric.js is not available when `init()` or another canvas operation needs it.
468
558
  - The configured canvas element cannot be found.
469
- - The image data URL is invalid, unsupported, too large, or times out while loading.
559
+ - The image data URL or selected file is invalid, unsupported, too large, or times out while loading.
470
560
  - Another operation is already running, such as image loading, animation, crop, undo, redo, merge, or export.
471
561
  - The editor has been disposed before the operation completes.
472
- - The browser cannot create a canvas export because of platform, memory, or security restrictions.
473
- - A custom `fabricGenerator`, label callback, or external event handler throws an error.
562
+ - The requested export exceeds `maxExportPixels`, uses an invalid multiplier, or the browser cannot create a canvas export because of platform, memory, or security restrictions.
563
+ - A custom callback such as `onImageLoaded`, `onCreate`, `fabricGenerator`, `label.create`, or `label.getText` throws. These are isolated where possible and reported through `onWarning` without rolling back already committed successful work.
474
564
 
475
565
  ## Example Workflow
476
566
 
@@ -529,7 +619,7 @@ IE11 and old mobile Safari are not supported by the distributed build. If you ne
529
619
 
530
620
  ## Dependencies
531
621
 
532
- - **fabric.js v5.x** — Must be loaded before ImageEditor.
622
+ - **fabric.js v5.x** — Required peer dependency. Browser global usage needs `window.fabric` available before `init()`.
533
623
 
534
624
  ## License
535
625