canvico-editor 1.0.1
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/LICENSE +21 -0
- package/README.md +72 -0
- package/dist/CanvicoEditor.d.ts +87 -0
- package/dist/CanvicoEditor.d.ts.map +1 -0
- package/dist/CanvicoEditor.js +189 -0
- package/dist/CanvicoEditor.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/BaseModule.d.ts +56 -0
- package/dist/modules/BaseModule.d.ts.map +1 -0
- package/dist/modules/BaseModule.js +45 -0
- package/dist/modules/BaseModule.js.map +1 -0
- package/dist/modules/CropModule.d.ts +147 -0
- package/dist/modules/CropModule.d.ts.map +1 -0
- package/dist/modules/CropModule.js +267 -0
- package/dist/modules/CropModule.js.map +1 -0
- package/dist/modules/ResizeModule.d.ts +61 -0
- package/dist/modules/ResizeModule.d.ts.map +1 -0
- package/dist/modules/ResizeModule.js +82 -0
- package/dist/modules/ResizeModule.js.map +1 -0
- package/dist/types.d.ts +78 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/dom-helpers.d.ts +32 -0
- package/dist/utils/dom-helpers.d.ts.map +1 -0
- package/dist/utils/dom-manager.d.ts +66 -0
- package/dist/utils/dom-manager.d.ts.map +1 -0
- package/dist/utils/dom-manager.js +71 -0
- package/dist/utils/dom-manager.js.map +1 -0
- package/dist/utils/error-handler.d.ts +14 -0
- package/dist/utils/error-handler.d.ts.map +1 -0
- package/dist/utils/error-handler.js +14 -0
- package/dist/utils/error-handler.js.map +1 -0
- package/dist/utils/validation.d.ts +55 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +40 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 [Your Name or GitHub username]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Canvico Editor
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/canvico-editor)
|
|
4
|
+
|
|
5
|
+
A simple and extensible image editor built with TypeScript and the Canvas API. Designed with modularity and developer experience in mind, it separates DOM logic from business logic, provides strong typing, and includes robust error handling. Easily integrable into web projects and a showcase of advanced TypeScript patterns.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Image Loading**: Load images from a local file input, URL, or `File` object.
|
|
10
|
+
- **Resizing**: Dynamically resize the image with an option to keep the aspect ratio.
|
|
11
|
+
- **Cropping**: User-friendly crop tool with a movable and resizable selection box.
|
|
12
|
+
- **Saving**: Download the edited image as a PNG file.
|
|
13
|
+
- **Modularity**: Easily extend the editor with new modules (e.g., filters, text, etc.).
|
|
14
|
+
- **Robust Error Handling**: Centralized error management for consistent and descriptive messages.
|
|
15
|
+
- **Strong Typing**: Fully typed API for improved Developer Experience.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
Install the package using npm:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install canvico-editor
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
import { CanvicoEditor } from "canvico-editor";
|
|
29
|
+
|
|
30
|
+
const canvico = new CanvicoEditor({
|
|
31
|
+
containerSelector: `.canvico-containerr`,
|
|
32
|
+
imageFileInputSelector: ".input-upload-file",
|
|
33
|
+
resetEditsButtonSelector: "#resetEdit",
|
|
34
|
+
clearCanvasButtonSelector: "#cleanAll",
|
|
35
|
+
saveButtonSelector: "#saveBtn",
|
|
36
|
+
modules: {
|
|
37
|
+
resize: {
|
|
38
|
+
widthInputSelector: "#widthInput",
|
|
39
|
+
heightInputSelector: "#heightInput",
|
|
40
|
+
lockAspectRatioSelector: "#keepAspectRatio",
|
|
41
|
+
},
|
|
42
|
+
crop: {
|
|
43
|
+
activateButtonSelector: "#cropBtn",
|
|
44
|
+
applyButtonSelector: "#applyBtn",
|
|
45
|
+
frameColor: "#d84cb9",
|
|
46
|
+
outsideOverlayColor: "rgba(0,0,0,0.2)",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Documentation
|
|
53
|
+
|
|
54
|
+
For the full API documentation, including all available options, methods, and modules, please visit the [documentation page](https://galacticbyte.github.io/canvico-editor/).
|
|
55
|
+
|
|
56
|
+
## Browser Support
|
|
57
|
+
|
|
58
|
+
| Chrome | Firefox | Safari | Edge |
|
|
59
|
+
| :----: | :-----: | :----: | :--: |
|
|
60
|
+
| Yes | Yes | Yes | Yes |
|
|
61
|
+
|
|
62
|
+
## Contributing
|
|
63
|
+
|
|
64
|
+
First off, thank you for considering contributing to Canvico Editor! It's people like you that make the open-source community such a great place.
|
|
65
|
+
|
|
66
|
+
Canvico Editor is an open-source project with a modular architecture, and we welcome any form of contribution. All help, from reporting bugs to adding new features (like custom modules), is highly appreciated.
|
|
67
|
+
|
|
68
|
+
If you have an idea for an improvement or have found a bug, the best way to contribute is by opening an "issue" or creating a "pull request". For detailed information on how you can help, please see the contribution guide: [CONTRIBUTING.md](https://galacticbyte.github.io/canvico-editor/docs/CONTRIBUTING/).
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
This project is available under the MIT license.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { CanvicoEditorConfig } from './types.js';
|
|
2
|
+
export declare class CanvicoEditor {
|
|
3
|
+
/** Manages all DOM element interactions and selections. */
|
|
4
|
+
private dom;
|
|
5
|
+
private canvas;
|
|
6
|
+
private ctx;
|
|
7
|
+
/** The original, unmodified image loaded by the user. */
|
|
8
|
+
private initialImage?;
|
|
9
|
+
/** The image currently being displayed and edited, including all modifications. */
|
|
10
|
+
private currentImage?;
|
|
11
|
+
/** A map holding all registered and initialized modules. */
|
|
12
|
+
private modules;
|
|
13
|
+
/** Handles and logs errors that occur within the editor. */
|
|
14
|
+
private errorHandler;
|
|
15
|
+
/** The configuration options passed to the editor upon instantiation. */
|
|
16
|
+
private config;
|
|
17
|
+
private DEFAULT_MODULE;
|
|
18
|
+
private activeModuleName;
|
|
19
|
+
/**
|
|
20
|
+
* Creates an instance of CanvasImageEditor.
|
|
21
|
+
* @param config - The configuration object for the editor.
|
|
22
|
+
* @throws {Error} If a required feature like FileReader is not supported by the browser.
|
|
23
|
+
*/
|
|
24
|
+
constructor(config: CanvicoEditorConfig);
|
|
25
|
+
/**
|
|
26
|
+
* Creates the canvas element and its 2D rendering context.
|
|
27
|
+
* @returns A tuple containing the canvas and its context.
|
|
28
|
+
* @throws {Error} If the 2D context cannot be created.
|
|
29
|
+
*/
|
|
30
|
+
private _initializeCanvas;
|
|
31
|
+
/**
|
|
32
|
+
* Cleans up all resources, event listeners, and modules to safely remove the editor instance.
|
|
33
|
+
*/
|
|
34
|
+
destroy(): void;
|
|
35
|
+
/**
|
|
36
|
+
* Resets the current image to its original state, discarding all changes.
|
|
37
|
+
*/
|
|
38
|
+
private _resetImage;
|
|
39
|
+
/**
|
|
40
|
+
* Clears the canvas and resets the entire editor state, including loaded images and module states.
|
|
41
|
+
*/
|
|
42
|
+
private _cleanAll;
|
|
43
|
+
/**
|
|
44
|
+
* Triggers a download of the current canvas content as a PNG image.
|
|
45
|
+
*/
|
|
46
|
+
private _saveImage;
|
|
47
|
+
/**
|
|
48
|
+
* Binds event listeners to the main control elements like file input, save, and reset buttons.
|
|
49
|
+
*/
|
|
50
|
+
private _bindGlobalEvents;
|
|
51
|
+
/**
|
|
52
|
+
* Initializes and registers all available modules based on the provided options.
|
|
53
|
+
*/
|
|
54
|
+
private _registerModules;
|
|
55
|
+
/**
|
|
56
|
+
* Resets the canvas to display the given image, scaled to fit the container.
|
|
57
|
+
* @param {HTMLImageElement} image - The image to display.
|
|
58
|
+
* @internal
|
|
59
|
+
*/
|
|
60
|
+
private _resetCanvasView;
|
|
61
|
+
/**
|
|
62
|
+
* Sets which module is currently active, deactivating all others.
|
|
63
|
+
* If the same module is activated again (and it's not the default), it toggles it off,
|
|
64
|
+
* returning to the default module.
|
|
65
|
+
* @param {ModuleName | null} moduleName - The name of the module to activate, or null to deactivate all.
|
|
66
|
+
* @internal
|
|
67
|
+
*/
|
|
68
|
+
private _setActiveModule;
|
|
69
|
+
/**
|
|
70
|
+
* Handles the file input change event to load, validate, and display an image.
|
|
71
|
+
* @param {Event} event - The file input change event.
|
|
72
|
+
* @internal
|
|
73
|
+
*/
|
|
74
|
+
private _loadImage;
|
|
75
|
+
/**
|
|
76
|
+
* Callback for the CropModule after a crop is applied. Updates the current image with the cropped version.
|
|
77
|
+
* @param {string} newImageDataUrl - The data URL of the newly cropped image.
|
|
78
|
+
* @internal
|
|
79
|
+
*/
|
|
80
|
+
private _handleCropApplied;
|
|
81
|
+
/**
|
|
82
|
+
* Clears the canvas, redraws the base image, and then draws the overlay for the currently active module.
|
|
83
|
+
* @internal
|
|
84
|
+
*/
|
|
85
|
+
private _redraw;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=CanvicoEditor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CanvicoEditor.d.ts","sourceRoot":"","sources":["../src/CanvicoEditor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAW,MAAM,YAAY,CAAC;AAc/D,qBAAa,aAAa;IACtB,2DAA2D;IAC3D,OAAO,CAAC,GAAG,CAAa;IAExB,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,GAAG,CAA2B;IAEtC,yDAAyD;IACzD,OAAO,CAAC,YAAY,CAAC,CAAmB;IACxC,mFAAmF;IACnF,OAAO,CAAC,YAAY,CAAC,CAAmB;IAExC,4DAA4D;IAC5D,OAAO,CAAC,OAAO,CAAmC;IAElD,4DAA4D;IAC5D,OAAO,CAAC,YAAY,CAAe;IAEnC,yEAAyE;IACzE,OAAO,CAAC,MAAM,CAAsB;IAEpC,OAAO,CAAC,cAAc,CAA2B;IACjD,OAAO,CAAC,gBAAgB,CAA2B;IAEnD;;;;OAIG;gBACS,MAAM,EAAE,mBAAmB;IAgBvC;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAczB;;OAEG;IACI,OAAO,IAAI,IAAI;IAatB;;OAEG;IACH,OAAO,CAAC,WAAW;IAQnB;;OAEG;IACH,OAAO,CAAC,SAAS;IAoBjB;;OAEG;IACH,OAAO,CAAC,UAAU;IAelB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAezB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAqCxB;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IAqBxB;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IA4BxB;;;;OAIG;IACH,OAAO,CAAC,UAAU;IAsClB;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAc1B;;;OAGG;IACH,OAAO,CAAC,OAAO;CAclB"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { ErrorHandler as h } from "./utils/error-handler.js";
|
|
2
|
+
import { DOMManager as l } from "./utils/dom-manager.js";
|
|
3
|
+
import { createFeatureNotSupportedError as c, createCanvasContextError as d, createImageSaveError as m, validateFile as u, createImageLoadError as n } from "./utils/validation.js";
|
|
4
|
+
import { ResizeModule as g } from "./modules/ResizeModule.js";
|
|
5
|
+
import { CropModule as v } from "./modules/CropModule.js";
|
|
6
|
+
class M {
|
|
7
|
+
/** Manages all DOM element interactions and selections. */
|
|
8
|
+
dom;
|
|
9
|
+
canvas;
|
|
10
|
+
ctx;
|
|
11
|
+
/** The original, unmodified image loaded by the user. */
|
|
12
|
+
initialImage;
|
|
13
|
+
/** The image currently being displayed and edited, including all modifications. */
|
|
14
|
+
currentImage;
|
|
15
|
+
/** A map holding all registered and initialized modules. */
|
|
16
|
+
modules = /* @__PURE__ */ new Map();
|
|
17
|
+
/** Handles and logs errors that occur within the editor. */
|
|
18
|
+
errorHandler;
|
|
19
|
+
/** The configuration options passed to the editor upon instantiation. */
|
|
20
|
+
config;
|
|
21
|
+
DEFAULT_MODULE = null;
|
|
22
|
+
activeModuleName = null;
|
|
23
|
+
/**
|
|
24
|
+
* Creates an instance of CanvasImageEditor.
|
|
25
|
+
* @param config - The configuration object for the editor.
|
|
26
|
+
* @throws {Error} If a required feature like FileReader is not supported by the browser.
|
|
27
|
+
*/
|
|
28
|
+
constructor(e) {
|
|
29
|
+
if (this.config = e, this.errorHandler = new h(), !window.FileReader)
|
|
30
|
+
throw c("FileReader API is not supported by this browser.");
|
|
31
|
+
this.dom = new l(e, this.errorHandler), [this.canvas, this.ctx] = this._initializeCanvas(), this._registerModules(), this._bindGlobalEvents();
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Creates the canvas element and its 2D rendering context.
|
|
35
|
+
* @returns A tuple containing the canvas and its context.
|
|
36
|
+
* @throws {Error} If the 2D context cannot be created.
|
|
37
|
+
*/
|
|
38
|
+
_initializeCanvas() {
|
|
39
|
+
const e = document.createElement("canvas"), t = e.getContext("2d");
|
|
40
|
+
if (!t)
|
|
41
|
+
throw d();
|
|
42
|
+
return this.dom.elements.container.appendChild(e), [e, t];
|
|
43
|
+
}
|
|
44
|
+
// --- Public API & User Actions ---
|
|
45
|
+
/**
|
|
46
|
+
* Cleans up all resources, event listeners, and modules to safely remove the editor instance.
|
|
47
|
+
*/
|
|
48
|
+
destroy() {
|
|
49
|
+
this._setActiveModule(null), this.dom.elements.imageFileInput.replaceWith(this.dom.elements.imageFileInput.cloneNode(!0)), this.dom.elements.clearCanvasButton.replaceWith(this.dom.elements.clearCanvasButton.cloneNode(!0)), this.dom.elements.saveButton.replaceWith(this.dom.elements.saveButton.cloneNode(!0)), this.modules.forEach((e) => e.destroy()), this.modules.clear();
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Resets the current image to its original state, discarding all changes.
|
|
53
|
+
*/
|
|
54
|
+
_resetImage() {
|
|
55
|
+
this.initialImage && (this.currentImage = this.initialImage, this._resetCanvasView(this.initialImage), this._setActiveModule(this.DEFAULT_MODULE));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Clears the canvas and resets the entire editor state, including loaded images and module states.
|
|
59
|
+
*/
|
|
60
|
+
_cleanAll() {
|
|
61
|
+
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height), this.canvas.width = 0, this.canvas.height = 0, this.initialImage = void 0, this.currentImage = void 0, this.dom.resizeElements && (this.dom.resizeElements.widthInput.value = "", this.dom.resizeElements.heightInput.value = "", this.dom.resizeElements.lockAspectRatio && (this.dom.resizeElements.lockAspectRatio.checked = !1)), this.dom.elements.imageFileInput.value = "", this._setActiveModule(this.DEFAULT_MODULE);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Triggers a download of the current canvas content as a PNG image.
|
|
65
|
+
*/
|
|
66
|
+
_saveImage() {
|
|
67
|
+
if (!this.currentImage) {
|
|
68
|
+
this.errorHandler.handle(m());
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const e = document.createElement("a"), t = this.dom.elements.imageFileInput.files?.[0], i = t ? t.name.replace(/\.[^/.]+$/, "") : "image";
|
|
72
|
+
e.download = `${i}-edited.png`, e.href = this.canvas.toDataURL("image/png"), e.click();
|
|
73
|
+
}
|
|
74
|
+
// --- Initialization Methods ---
|
|
75
|
+
/**
|
|
76
|
+
* Binds event listeners to the main control elements like file input, save, and reset buttons.
|
|
77
|
+
*/
|
|
78
|
+
_bindGlobalEvents() {
|
|
79
|
+
this.dom.elements.imageFileInput.addEventListener("change", (e) => this._loadImage(e)), this.dom.cropElements && this.dom.cropElements.activateButton.addEventListener("click", () => this.currentImage && this._setActiveModule(
|
|
80
|
+
"crop"
|
|
81
|
+
/* CROP */
|
|
82
|
+
)), this.dom.elements.resetEditsButton.addEventListener("click", () => this._resetImage()), this.dom.elements.clearCanvasButton.addEventListener("click", () => this._cleanAll()), this.dom.elements.saveButton.addEventListener("click", () => this._saveImage());
|
|
83
|
+
}
|
|
84
|
+
// --- Core Drawing & State Logic ---
|
|
85
|
+
/**
|
|
86
|
+
* Initializes and registers all available modules based on the provided options.
|
|
87
|
+
*/
|
|
88
|
+
_registerModules() {
|
|
89
|
+
if (this.dom.resizeElements) {
|
|
90
|
+
const e = new g(this.dom.resizeElements, {
|
|
91
|
+
container: this.dom.elements.container,
|
|
92
|
+
canvas: this.canvas,
|
|
93
|
+
ctx: this.ctx,
|
|
94
|
+
getCurrentImage: () => this.currentImage
|
|
95
|
+
});
|
|
96
|
+
this.modules.set("resize", e), this.DEFAULT_MODULE = "resize";
|
|
97
|
+
}
|
|
98
|
+
if (this.dom.cropElements && this.config.modules?.crop) {
|
|
99
|
+
const e = new v(this.dom.cropElements, {
|
|
100
|
+
canvas: this.canvas,
|
|
101
|
+
ctx: this.ctx,
|
|
102
|
+
frameColor: this.config.modules.crop.frameColor,
|
|
103
|
+
outsideOverlayColor: this.config.modules.crop.outsideOverlayColor,
|
|
104
|
+
requestRedraw: () => this._redraw(),
|
|
105
|
+
getCurrentImage: () => this.currentImage,
|
|
106
|
+
onCropApplied: (t) => this._handleCropApplied(t)
|
|
107
|
+
});
|
|
108
|
+
this.modules.set("crop", e);
|
|
109
|
+
}
|
|
110
|
+
this.modules.forEach((e) => e.init()), console.log(this.modules), this._setActiveModule(this.DEFAULT_MODULE);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Resets the canvas to display the given image, scaled to fit the container.
|
|
114
|
+
* @param {HTMLImageElement} image - The image to display.
|
|
115
|
+
* @internal
|
|
116
|
+
*/
|
|
117
|
+
_resetCanvasView(e) {
|
|
118
|
+
this.currentImage = e;
|
|
119
|
+
const t = this.dom.elements.container.clientWidth, i = this.dom.elements.container.clientHeight, r = Math.min(t / e.width, i / e.height), a = e.width * r, s = e.height * r;
|
|
120
|
+
this.canvas.width = a, this.canvas.height = s, this._redraw(), this.dom.resizeElements && (this.dom.resizeElements.widthInput.value = Math.round(a).toString(), this.dom.resizeElements.heightInput.value = Math.round(s).toString());
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Sets which module is currently active, deactivating all others.
|
|
124
|
+
* If the same module is activated again (and it's not the default), it toggles it off,
|
|
125
|
+
* returning to the default module.
|
|
126
|
+
* @param {ModuleName | null} moduleName - The name of the module to activate, or null to deactivate all.
|
|
127
|
+
* @internal
|
|
128
|
+
*/
|
|
129
|
+
_setActiveModule(e) {
|
|
130
|
+
let t = e;
|
|
131
|
+
this.activeModuleName === e && e !== this.DEFAULT_MODULE && (t = this.DEFAULT_MODULE), this.activeModuleName !== t && (this.activeModuleName = t, this.modules.forEach((i, r) => {
|
|
132
|
+
r === this.activeModuleName ? i.activate() : i.deactivate();
|
|
133
|
+
}), this._redraw());
|
|
134
|
+
}
|
|
135
|
+
// --- Event Handlers & Callbacks ---
|
|
136
|
+
/**
|
|
137
|
+
* Handles the file input change event to load, validate, and display an image.
|
|
138
|
+
* @param {Event} event - The file input change event.
|
|
139
|
+
* @internal
|
|
140
|
+
*/
|
|
141
|
+
_loadImage(e) {
|
|
142
|
+
try {
|
|
143
|
+
const i = e.target.files?.[0];
|
|
144
|
+
if (!i)
|
|
145
|
+
return;
|
|
146
|
+
u(i, this.config.maxFileSizeMB || 5);
|
|
147
|
+
const r = new FileReader();
|
|
148
|
+
r.onload = (a) => {
|
|
149
|
+
const s = new Image();
|
|
150
|
+
s.onload = () => {
|
|
151
|
+
try {
|
|
152
|
+
this.currentImage = s, this.initialImage = s, this._resetCanvasView(s);
|
|
153
|
+
} catch (o) {
|
|
154
|
+
this.errorHandler.handle(o);
|
|
155
|
+
}
|
|
156
|
+
}, s.onerror = () => {
|
|
157
|
+
this.errorHandler.handle(n("Error reading image file."));
|
|
158
|
+
}, s.src = a.target.result;
|
|
159
|
+
}, r.onerror = () => {
|
|
160
|
+
this.errorHandler.handle(n("Error reading file with FileReader."));
|
|
161
|
+
}, r.readAsDataURL(i);
|
|
162
|
+
} catch (t) {
|
|
163
|
+
this.errorHandler.handle(t);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Callback for the CropModule after a crop is applied. Updates the current image with the cropped version.
|
|
168
|
+
* @param {string} newImageDataUrl - The data URL of the newly cropped image.
|
|
169
|
+
* @internal
|
|
170
|
+
*/
|
|
171
|
+
_handleCropApplied(e) {
|
|
172
|
+
const t = new Image();
|
|
173
|
+
t.onload = () => {
|
|
174
|
+
this.currentImage = t, this._setActiveModule(this.DEFAULT_MODULE), this._resetCanvasView(t);
|
|
175
|
+
}, t.src = e;
|
|
176
|
+
}
|
|
177
|
+
// --- Module Management ---
|
|
178
|
+
/**
|
|
179
|
+
* Clears the canvas, redraws the base image, and then draws the overlay for the currently active module.
|
|
180
|
+
* @internal
|
|
181
|
+
*/
|
|
182
|
+
_redraw() {
|
|
183
|
+
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height), this.currentImage && (this.ctx.drawImage(this.currentImage, 0, 0, this.currentImage.width, this.currentImage.height, 0, 0, this.canvas.width, this.canvas.height), this.activeModuleName && this.modules.get(this.activeModuleName)?.drawOverlay?.());
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
export {
|
|
187
|
+
M as CanvicoEditor
|
|
188
|
+
};
|
|
189
|
+
//# sourceMappingURL=CanvicoEditor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CanvicoEditor.js","sources":["../src/CanvicoEditor.ts"],"sourcesContent":["import type { CanvicoEditorConfig, IModule } from \"./types.js\";\r\nimport { ErrorHandler } from \"./utils/error-handler.js\";\r\nimport { DOMManager } from \"./utils/dom-manager.js\";\r\nimport { validateFile, createCanvasContextError, createImageLoadError, createImageSaveError, createFeatureNotSupportedError } from \"./utils/validation.js\";\r\n\r\n// Modules\r\nimport { ResizeModule } from \"./modules/ResizeModule.js\";\r\nimport { CropModule } from \"./modules/CropModule.js\";\r\n\r\nenum ModuleName {\r\n RESIZE = \"resize\",\r\n CROP = \"crop\",\r\n}\r\n\r\nexport class CanvicoEditor {\r\n /** Manages all DOM element interactions and selections. */\r\n private dom: DOMManager;\r\n\r\n private canvas: HTMLCanvasElement;\r\n private ctx: CanvasRenderingContext2D;\r\n\r\n /** The original, unmodified image loaded by the user. */\r\n private initialImage?: HTMLImageElement;\r\n /** The image currently being displayed and edited, including all modifications. */\r\n private currentImage?: HTMLImageElement;\r\n\r\n /** A map holding all registered and initialized modules. */\r\n private modules: Map<string, IModule> = new Map();\r\n\r\n /** Handles and logs errors that occur within the editor. */\r\n private errorHandler: ErrorHandler;\r\n\r\n /** The configuration options passed to the editor upon instantiation. */\r\n private config: CanvicoEditorConfig;\r\n\r\n private DEFAULT_MODULE: ModuleName | null = null;\r\n private activeModuleName: ModuleName | null = null;\r\n\r\n /**\r\n * Creates an instance of CanvasImageEditor.\r\n * @param config - The configuration object for the editor.\r\n * @throws {Error} If a required feature like FileReader is not supported by the browser.\r\n */\r\n constructor(config: CanvicoEditorConfig) {\r\n // Assign properties first, so they are available in case of an early error\r\n this.config = config;\r\n this.errorHandler = new ErrorHandler();\r\n\r\n if (!window.FileReader) {\r\n throw createFeatureNotSupportedError(\"FileReader API is not supported by this browser.\");\r\n }\r\n\r\n // All initializations\r\n this.dom = new DOMManager(config, this.errorHandler); // Initialize and validate DOM elements\r\n [this.canvas, this.ctx] = this._initializeCanvas(); // Create canvas and get context 2d\r\n this._registerModules();\r\n this._bindGlobalEvents();\r\n }\r\n\r\n /**\r\n * Creates the canvas element and its 2D rendering context.\r\n * @returns A tuple containing the canvas and its context.\r\n * @throws {Error} If the 2D context cannot be created.\r\n */\r\n private _initializeCanvas(): [HTMLCanvasElement, CanvasRenderingContext2D] {\r\n const canvas = document.createElement(\"canvas\");\r\n const ctx = canvas.getContext(\"2d\");\r\n\r\n if (!ctx) {\r\n throw createCanvasContextError();\r\n }\r\n this.dom.elements.container.appendChild(canvas);\r\n\r\n return [canvas, ctx];\r\n }\r\n\r\n // --- Public API & User Actions ---\r\n\r\n /**\r\n * Cleans up all resources, event listeners, and modules to safely remove the editor instance.\r\n */\r\n public destroy(): void {\r\n // Deactivate any active module\r\n this._setActiveModule(null);\r\n\r\n // A simple way to remove all listeners is to replace the nodes.\r\n this.dom.elements.imageFileInput.replaceWith(this.dom.elements.imageFileInput.cloneNode(true));\r\n this.dom.elements.clearCanvasButton.replaceWith(this.dom.elements.clearCanvasButton.cloneNode(true));\r\n this.dom.elements.saveButton.replaceWith(this.dom.elements.saveButton.cloneNode(true));\r\n\r\n this.modules.forEach((module) => module.destroy());\r\n this.modules.clear();\r\n }\r\n\r\n /**\r\n * Resets the current image to its original state, discarding all changes.\r\n */\r\n private _resetImage(): void {\r\n if (this.initialImage) {\r\n this.currentImage = this.initialImage;\r\n this._resetCanvasView(this.initialImage);\r\n this._setActiveModule(this.DEFAULT_MODULE);\r\n }\r\n }\r\n\r\n /**\r\n * Clears the canvas and resets the entire editor state, including loaded images and module states.\r\n */\r\n private _cleanAll(): void {\r\n this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);\r\n this.canvas.width = 0;\r\n this.canvas.height = 0;\r\n\r\n this.initialImage = undefined;\r\n this.currentImage = undefined;\r\n\r\n // Reset input fields\r\n if (this.dom.resizeElements) {\r\n this.dom.resizeElements.widthInput.value = \"\";\r\n this.dom.resizeElements.heightInput.value = \"\";\r\n if (this.dom.resizeElements.lockAspectRatio) this.dom.resizeElements.lockAspectRatio.checked = false;\r\n }\r\n\r\n this.dom.elements.imageFileInput.value = \"\";\r\n\r\n this._setActiveModule(this.DEFAULT_MODULE);\r\n }\r\n\r\n /**\r\n * Triggers a download of the current canvas content as a PNG image.\r\n */\r\n private _saveImage(): void {\r\n if (!this.currentImage) {\r\n this.errorHandler.handle(createImageSaveError());\r\n return;\r\n }\r\n const link = document.createElement(\"a\");\r\n const originalFile = this.dom.elements.imageFileInput.files?.[0];\r\n const baseName = originalFile ? originalFile.name.replace(/\\.[^/.]+$/, \"\") : \"image\";\r\n link.download = `${baseName}-edited.png`;\r\n link.href = this.canvas.toDataURL(\"image/png\");\r\n link.click();\r\n }\r\n\r\n // --- Initialization Methods ---\r\n\r\n /**\r\n * Binds event listeners to the main control elements like file input, save, and reset buttons.\r\n */\r\n private _bindGlobalEvents(): void {\r\n this.dom.elements.imageFileInput.addEventListener(\"change\", (e: Event) => this._loadImage(e));\r\n\r\n // Bind module buttons\r\n if (this.dom.cropElements) {\r\n this.dom.cropElements.activateButton.addEventListener(\"click\", () => this.currentImage && this._setActiveModule(ModuleName.CROP));\r\n }\r\n\r\n this.dom.elements.resetEditsButton.addEventListener(\"click\", () => this._resetImage());\r\n this.dom.elements.clearCanvasButton.addEventListener(\"click\", () => this._cleanAll());\r\n this.dom.elements.saveButton.addEventListener(\"click\", () => this._saveImage());\r\n }\r\n\r\n // --- Core Drawing & State Logic ---\r\n\r\n /**\r\n * Initializes and registers all available modules based on the provided options.\r\n */\r\n private _registerModules(): void {\r\n // Register ResizeModule only if its essential inputs are provided\r\n if (this.dom.resizeElements) {\r\n const resizeModule = new ResizeModule(this.dom.resizeElements, {\r\n container: this.dom.elements.container,\r\n canvas: this.canvas,\r\n ctx: this.ctx,\r\n\r\n getCurrentImage: () => this.currentImage,\r\n });\r\n this.modules.set(ModuleName.RESIZE, resizeModule);\r\n this.DEFAULT_MODULE = ModuleName.RESIZE;\r\n }\r\n\r\n // Register CropModule only if its essential button is provided\r\n if (this.dom.cropElements && this.config.modules?.crop) {\r\n const cropModule = new CropModule(this.dom.cropElements, {\r\n canvas: this.canvas,\r\n ctx: this.ctx,\r\n frameColor: this.config.modules.crop.frameColor,\r\n outsideOverlayColor: this.config.modules.crop.outsideOverlayColor,\r\n requestRedraw: () => this._redraw(),\r\n getCurrentImage: () => this.currentImage,\r\n onCropApplied: (newImageDataUrl: string) => this._handleCropApplied(newImageDataUrl),\r\n });\r\n\r\n this.modules.set(ModuleName.CROP, cropModule);\r\n }\r\n\r\n // Initialize all registered modules\r\n this.modules.forEach((module) => module.init());\r\n console.log(this.modules);\r\n\r\n // Set the Resize module as active by default\r\n this._setActiveModule(this.DEFAULT_MODULE);\r\n }\r\n\r\n /**\r\n * Resets the canvas to display the given image, scaled to fit the container.\r\n * @param {HTMLImageElement} image - The image to display.\r\n * @internal\r\n */\r\n private _resetCanvasView(image: HTMLImageElement): void {\r\n this.currentImage = image;\r\n\r\n const containerWidth = this.dom.elements.container.clientWidth;\r\n const containerHeight = this.dom.elements.container.clientHeight;\r\n const scale = Math.min(containerWidth / image.width, containerHeight / image.height);\r\n\r\n const drawW = image.width * scale;\r\n const drawH = image.height * scale;\r\n\r\n this.canvas.width = drawW;\r\n this.canvas.height = drawH;\r\n\r\n this._redraw();\r\n\r\n if (this.dom.resizeElements) {\r\n this.dom.resizeElements.widthInput.value = Math.round(drawW).toString();\r\n this.dom.resizeElements.heightInput.value = Math.round(drawH).toString();\r\n }\r\n }\r\n\r\n /**\r\n * Sets which module is currently active, deactivating all others.\r\n * If the same module is activated again (and it's not the default), it toggles it off,\r\n * returning to the default module.\r\n * @param {ModuleName | null} moduleName - The name of the module to activate, or null to deactivate all.\r\n * @internal\r\n */\r\n private _setActiveModule(moduleName: ModuleName | null): void {\r\n let newActiveModuleName = moduleName;\r\n\r\n // If we try to activate a module that is already active (and it's not the default module),\r\n // we switch back to the default module. This creates a 'toggle' effect.\r\n if (this.activeModuleName === moduleName && moduleName !== this.DEFAULT_MODULE) {\r\n newActiveModuleName = this.DEFAULT_MODULE;\r\n }\r\n\r\n // If the state doesn't change, do nothing.\r\n if (this.activeModuleName === newActiveModuleName) {\r\n return;\r\n }\r\n\r\n this.activeModuleName = newActiveModuleName;\r\n\r\n this.modules.forEach((module, name) => {\r\n if (name === this.activeModuleName) {\r\n module.activate();\r\n } else {\r\n module.deactivate();\r\n }\r\n });\r\n this._redraw();\r\n }\r\n\r\n // --- Event Handlers & Callbacks ---\r\n\r\n /**\r\n * Handles the file input change event to load, validate, and display an image.\r\n * @param {Event} event - The file input change event.\r\n * @internal\r\n */\r\n private _loadImage(event: Event): void {\r\n // If no file input element is available, do nothing\r\n try {\r\n const target = event.target as HTMLInputElement;\r\n const file = target.files?.[0];\r\n if (!file) {\r\n return;\r\n }\r\n\r\n validateFile(file, this.config.maxFileSizeMB || 5);\r\n\r\n const reader = new FileReader();\r\n reader.onload = (e) => {\r\n const img = new Image();\r\n img.onload = () => {\r\n try {\r\n this.currentImage = img;\r\n this.initialImage = img;\r\n\r\n this._resetCanvasView(img);\r\n } catch (error) {\r\n this.errorHandler.handle(error as Error);\r\n }\r\n };\r\n img.onerror = () => {\r\n this.errorHandler.handle(createImageLoadError(\"Error reading image file.\"));\r\n };\r\n img.src = e.target!.result as string;\r\n };\r\n reader.onerror = () => {\r\n this.errorHandler.handle(createImageLoadError(\"Error reading file with FileReader.\"));\r\n };\r\n reader.readAsDataURL(file);\r\n } catch (error) {\r\n this.errorHandler.handle(error as Error);\r\n }\r\n }\r\n\r\n /**\r\n * Callback for the CropModule after a crop is applied. Updates the current image with the cropped version.\r\n * @param {string} newImageDataUrl - The data URL of the newly cropped image.\r\n * @internal\r\n */\r\n private _handleCropApplied(newImageDataUrl: string): void {\r\n const newImg = new Image();\r\n newImg.onload = () => {\r\n this.currentImage = newImg;\r\n\r\n this._setActiveModule(this.DEFAULT_MODULE);\r\n\r\n this._resetCanvasView(newImg);\r\n };\r\n newImg.src = newImageDataUrl;\r\n }\r\n\r\n // --- Module Management ---\r\n\r\n /**\r\n * Clears the canvas, redraws the base image, and then draws the overlay for the currently active module.\r\n * @internal\r\n */\r\n private _redraw(): void {\r\n this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);\r\n\r\n if (!this.currentImage) {\r\n return;\r\n }\r\n this.ctx.drawImage(this.currentImage, 0, 0, this.currentImage.width, this.currentImage.height, 0, 0, this.canvas.width, this.canvas.height);\r\n\r\n if (this.activeModuleName) {\r\n const activeModule = this.modules.get(this.activeModuleName);\r\n // The 'drawOverlay' method is optional, so we check for its existence before calling.\r\n activeModule?.drawOverlay?.();\r\n }\r\n }\r\n}\r\n"],"names":["CanvicoEditor","config","ErrorHandler","createFeatureNotSupportedError","DOMManager","canvas","ctx","createCanvasContextError","module","createImageSaveError","link","originalFile","baseName","resizeModule","ResizeModule","cropModule","CropModule","newImageDataUrl","image","containerWidth","containerHeight","scale","drawW","drawH","moduleName","newActiveModuleName","name","event","file","validateFile","reader","e","img","error","createImageLoadError","newImg"],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,cAAc,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { IModule, ModuleEventHandler } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Base class for all editor modules. It provides a centralized way to manage
|
|
4
|
+
* event listeners, ensuring they are properly cleaned up when a module is destroyed.
|
|
5
|
+
*/
|
|
6
|
+
export declare abstract class BaseModule implements IModule {
|
|
7
|
+
/** The unique name of the module (e.g., 'crop', 'resize'). */
|
|
8
|
+
private readonly name;
|
|
9
|
+
/**
|
|
10
|
+
* Stores all registered event listeners for this module.
|
|
11
|
+
* Each entry contains the element, event type, and the handler function,
|
|
12
|
+
* allowing for easy removal in the `destroy` method.
|
|
13
|
+
*/
|
|
14
|
+
protected eventHandlers: Array<{
|
|
15
|
+
element: HTMLElement;
|
|
16
|
+
type: string;
|
|
17
|
+
handler: ModuleEventHandler;
|
|
18
|
+
}>;
|
|
19
|
+
/**
|
|
20
|
+
* @param name - The unique name for the module.
|
|
21
|
+
*/
|
|
22
|
+
constructor(name: string);
|
|
23
|
+
/**
|
|
24
|
+
* Initializes the module. Each module must implement this method
|
|
25
|
+
* to register its necessary event listeners using the `addEventListener` helper.
|
|
26
|
+
*/
|
|
27
|
+
abstract init(): void;
|
|
28
|
+
/**
|
|
29
|
+
* Activates the module, enabling its specific functionality.
|
|
30
|
+
* Each module must implement this method.
|
|
31
|
+
*/
|
|
32
|
+
abstract activate(): void;
|
|
33
|
+
/**
|
|
34
|
+
* Deactivates the module, disabling its functionality and cleaning up its active state.
|
|
35
|
+
* Each module must implement this method.
|
|
36
|
+
*/
|
|
37
|
+
abstract deactivate(): void;
|
|
38
|
+
/**
|
|
39
|
+
* Returns the unique name of the module.
|
|
40
|
+
* @returns The name of the module.
|
|
41
|
+
*/
|
|
42
|
+
getName(): string;
|
|
43
|
+
/**
|
|
44
|
+
* Cleans up all resources used by the module.
|
|
45
|
+
* This method iterates over all registered event listeners and removes them.
|
|
46
|
+
*/
|
|
47
|
+
destroy(): void;
|
|
48
|
+
/**
|
|
49
|
+
* A helper method to attach an event listener to an element and track it for later removal.
|
|
50
|
+
* @param el The element to attach the listener to (e.g., HTMLElement, HTMLInputElement).
|
|
51
|
+
* @param type The event name (e.g., "click", "mousedown", "input").
|
|
52
|
+
* @param handler The event handler function.
|
|
53
|
+
*/
|
|
54
|
+
protected addEventListener(el: HTMLElement, type: string, handler: ModuleEventHandler): void;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=BaseModule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BaseModule.d.ts","sourceRoot":"","sources":["../../src/modules/BaseModule.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAE/D;;;GAGG;AACH,8BAAsB,UAAW,YAAW,OAAO;IAC/C,8DAA8D;IAC9D,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B;;;;OAIG;IACH,SAAS,CAAC,aAAa,EAAE,KAAK,CAAC;QAC3B,OAAO,EAAE,WAAW,CAAC;QACrB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,kBAAkB,CAAC;KAC/B,CAAC,CAAM;IAER;;OAEG;gBACS,IAAI,EAAE,MAAM;IAIxB;;;OAGG;aACa,IAAI,IAAI,IAAI;IAE5B;;;OAGG;aACa,QAAQ,IAAI,IAAI;IAEhC;;;OAGG;aACa,UAAU,IAAI,IAAI;IAElC;;;OAGG;IACI,OAAO,IAAI,MAAM;IAIxB;;;OAGG;IACI,OAAO,IAAI,IAAI;IAOtB;;;;;OAKG;IACH,SAAS,CAAC,gBAAgB,CAAC,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,GAAG,IAAI;CAI/F"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
class r {
|
|
2
|
+
/** The unique name of the module (e.g., 'crop', 'resize'). */
|
|
3
|
+
name;
|
|
4
|
+
/**
|
|
5
|
+
* Stores all registered event listeners for this module.
|
|
6
|
+
* Each entry contains the element, event type, and the handler function,
|
|
7
|
+
* allowing for easy removal in the `destroy` method.
|
|
8
|
+
*/
|
|
9
|
+
eventHandlers = [];
|
|
10
|
+
/**
|
|
11
|
+
* @param name - The unique name for the module.
|
|
12
|
+
*/
|
|
13
|
+
constructor(e) {
|
|
14
|
+
this.name = e;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Returns the unique name of the module.
|
|
18
|
+
* @returns The name of the module.
|
|
19
|
+
*/
|
|
20
|
+
getName() {
|
|
21
|
+
return this.name;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Cleans up all resources used by the module.
|
|
25
|
+
* This method iterates over all registered event listeners and removes them.
|
|
26
|
+
*/
|
|
27
|
+
destroy() {
|
|
28
|
+
this.eventHandlers.forEach(({ element: e, type: t, handler: n }) => {
|
|
29
|
+
e.removeEventListener(t, n);
|
|
30
|
+
}), this.eventHandlers = [];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A helper method to attach an event listener to an element and track it for later removal.
|
|
34
|
+
* @param el The element to attach the listener to (e.g., HTMLElement, HTMLInputElement).
|
|
35
|
+
* @param type The event name (e.g., "click", "mousedown", "input").
|
|
36
|
+
* @param handler The event handler function.
|
|
37
|
+
*/
|
|
38
|
+
addEventListener(e, t, n) {
|
|
39
|
+
e.addEventListener(t, n), this.eventHandlers.push({ element: e, type: t, handler: n });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
r as BaseModule
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=BaseModule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BaseModule.js","sources":["../../src/modules/BaseModule.ts"],"sourcesContent":["import type { IModule, ModuleEventHandler } from \"../types.js\";\r\n\r\n/**\r\n * Base class for all editor modules. It provides a centralized way to manage\r\n * event listeners, ensuring they are properly cleaned up when a module is destroyed.\r\n */\r\nexport abstract class BaseModule implements IModule {\r\n /** The unique name of the module (e.g., 'crop', 'resize'). */\r\n private readonly name: string;\r\n /**\r\n * Stores all registered event listeners for this module.\r\n * Each entry contains the element, event type, and the handler function,\r\n * allowing for easy removal in the `destroy` method.\r\n */\r\n protected eventHandlers: Array<{\r\n element: HTMLElement;\r\n type: string;\r\n handler: ModuleEventHandler;\r\n }> = [];\r\n\r\n /**\r\n * @param name - The unique name for the module.\r\n */\r\n constructor(name: string) {\r\n this.name = name;\r\n }\r\n\r\n /**\r\n * Initializes the module. Each module must implement this method\r\n * to register its necessary event listeners using the `addEventListener` helper.\r\n */\r\n public abstract init(): void;\r\n\r\n /**\r\n * Activates the module, enabling its specific functionality.\r\n * Each module must implement this method.\r\n */\r\n public abstract activate(): void;\r\n\r\n /**\r\n * Deactivates the module, disabling its functionality and cleaning up its active state.\r\n * Each module must implement this method.\r\n */\r\n public abstract deactivate(): void;\r\n\r\n /**\r\n * Returns the unique name of the module.\r\n * @returns The name of the module.\r\n */\r\n public getName(): string {\r\n return this.name;\r\n }\r\n\r\n /**\r\n * Cleans up all resources used by the module.\r\n * This method iterates over all registered event listeners and removes them.\r\n */\r\n public destroy(): void {\r\n this.eventHandlers.forEach(({ element, type, handler }) => {\r\n element.removeEventListener(type, handler);\r\n });\r\n this.eventHandlers = [];\r\n }\r\n\r\n /**\r\n * A helper method to attach an event listener to an element and track it for later removal.\r\n * @param el The element to attach the listener to (e.g., HTMLElement, HTMLInputElement).\r\n * @param type The event name (e.g., \"click\", \"mousedown\", \"input\").\r\n * @param handler The event handler function.\r\n */\r\n protected addEventListener(el: HTMLElement, type: string, handler: ModuleEventHandler): void {\r\n el.addEventListener(type, handler);\r\n this.eventHandlers.push({ element: el, type, handler });\r\n }\r\n}\r\n"],"names":["BaseModule","name","element","type","handler","el"],"mappings":"AAMO,MAAeA,EAA8B;AAAA;AAAA,EAE/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMP,gBAIL,CAAA;AAAA;AAAA;AAAA;AAAA,EAKL,YAAYC,GAAc;AACtB,SAAK,OAAOA;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA,EAwBO,UAAkB;AACrB,WAAO,KAAK;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,UAAgB;AACnB,SAAK,cAAc,QAAQ,CAAC,EAAE,SAAAC,GAAS,MAAAC,GAAM,SAAAC,QAAc;AACvD,MAAAF,EAAQ,oBAAoBC,GAAMC,CAAO;AAAA,IAC7C,CAAC,GACD,KAAK,gBAAgB,CAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQU,iBAAiBC,GAAiBF,GAAcC,GAAmC;AACzF,IAAAC,EAAG,iBAAiBF,GAAMC,CAAO,GACjC,KAAK,cAAc,KAAK,EAAE,SAASC,GAAI,MAAAF,GAAM,SAAAC,GAAS;AAAA,EAC1D;AACJ;"}
|