@lemonadejs/cropper 5.0.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1 +1,334 @@
1
- # lemonadejs
1
+ # JavaScript Image Cropper - LemonadeJS Cropper Plugin
2
+
3
+ A lightweight, powerful **JavaScript image cropper** and photo editor component built with [LemonadeJS](https://lemonadejs.com). Crop, resize, rotate, and adjust images directly in your web browser with this modern, reactive image editing solution.
4
+
5
+ ## Key Features
6
+
7
+ - **Image Cropping**: Intuitive drag-and-drop interface for precise image cropping
8
+ - **Image Rotation**: Rotate images with smooth, real-time preview
9
+ - **Zoom Control**: Fine-grained zoom functionality from 0.1x to 5.45x
10
+ - **Image Adjustments**: Brightness and contrast controls for professional results
11
+ - **Responsive Design**: Automatically adapts to mobile and desktop screens
12
+ - **Modal Interface**: Clean, user-friendly modal dialog for editing
13
+ - **Blob Output**: Generates cropped images as blobs for easy upload
14
+ - **Original Image Preservation**: Optionally keep the original image alongside the cropped version
15
+ - **TypeScript Support**: Full TypeScript definitions included
16
+ - **Framework Integration**: Works with vanilla JavaScript, React, Vue, and any modern framework
17
+
18
+
19
+ ## Installation
20
+
21
+ ### NPM Installation
22
+
23
+ ```bash
24
+ npm install @lemonadejs/cropper
25
+ ```
26
+
27
+ ### CDN Installation
28
+
29
+ ```html
30
+ <!-- LemonadeJS Core -->
31
+ <script src="https://cdn.jsdelivr.net/npm/lemonadejs/dist/lemonade.min.js"></script>
32
+
33
+ <!-- jSuites Cropper (dependency) -->
34
+ <script src="https://cdn.jsdelivr.net/npm/@jsuites/cropper/cropper.min.js"></script>
35
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jsuites/cropper/cropper.min.css" />
36
+
37
+ <!-- LemonadeJS Cropper Plugin -->
38
+ <script src="https://cdn.jsdelivr.net/npm/@lemonadejs/cropper/dist/index.js"></script>
39
+ ```
40
+
41
+ ## Quick Start Example
42
+
43
+ ### Basic Image Cropper
44
+
45
+ ```html
46
+ <!DOCTYPE html>
47
+ <html>
48
+ <head>
49
+ <script src="https://cdn.jsdelivr.net/npm/lemonadejs/dist/lemonade.min.js"></script>
50
+ <script src="https://cdn.jsdelivr.net/npm/@jsuites/cropper/cropper.min.js"></script>
51
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jsuites/cropper/cropper.min.css" />
52
+ <script src="https://cdn.jsdelivr.net/npm/@lemonadejs/cropper/dist/index.js"></script>
53
+ </head>
54
+ <body>
55
+ <div id="root"></div>
56
+
57
+ <script>
58
+ const root = document.getElementById('root');
59
+ const cropper = Cropper(root, {
60
+ width: 300,
61
+ height: 240
62
+ });
63
+ </script>
64
+ </body>
65
+ </html>
66
+ ```
67
+
68
+ ### Using with LemonadeJS Component
69
+
70
+ ```javascript
71
+ import lemonade from 'lemonadejs';
72
+ import Cropper from '@lemonadejs/cropper';
73
+
74
+ export default function MyApp() {
75
+ const self = this;
76
+
77
+ return `<div>
78
+ <h1>Profile Picture Editor</h1>
79
+ <Cropper
80
+ width="400"
81
+ height="300"
82
+ :ref="self.cropper"
83
+ original="true" />
84
+ <button onclick="self.cropper.open()">Edit Photo</button>
85
+ </div>`;
86
+ }
87
+ ```
88
+
89
+ ### React Integration
90
+
91
+ ```javascript
92
+ import React, { useRef, useEffect } from 'react';
93
+ import Cropper from '@lemonadejs/cropper';
94
+
95
+ function PhotoEditor() {
96
+ const cropperRef = useRef(null);
97
+ const containerRef = useRef(null);
98
+
99
+ useEffect(() => {
100
+ if (containerRef.current) {
101
+ cropperRef.current = Cropper(containerRef.current, {
102
+ width: 400,
103
+ height: 300,
104
+ original: true
105
+ });
106
+ }
107
+
108
+ return () => {
109
+ // Cleanup if needed
110
+ };
111
+ }, []);
112
+
113
+ const handleGetImage = () => {
114
+ if (cropperRef.current) {
115
+ const imageData = cropperRef.current.getValue();
116
+ console.log('Image data:', imageData);
117
+ }
118
+ };
119
+
120
+ return (
121
+ <div>
122
+ <div ref={containerRef}></div>
123
+ <button onClick={handleGetImage}>Get Image Data</button>
124
+ </div>
125
+ );
126
+ }
127
+
128
+ export default PhotoEditor;
129
+ ```
130
+
131
+ ### Advanced Configuration
132
+
133
+ ```javascript
134
+ const cropper = Cropper(document.getElementById('root'), {
135
+ width: 500, // Crop area width
136
+ height: 400, // Crop area height
137
+ original: true, // Preserve original image
138
+ name: 'profile_pic' // Form field name
139
+ });
140
+
141
+ // Set an image programmatically
142
+ cropper.setValue({
143
+ file: 'https://example.com/image.jpg'
144
+ });
145
+
146
+ // Get cropped image data
147
+ const imageData = cropper.getValue();
148
+ console.log(imageData);
149
+ // Output: [{ file: 'blob:...', content: 'data:image/png;base64,...', extension: 'png' }]
150
+ ```
151
+
152
+ ## Usage Examples
153
+
154
+ ### Profile Picture Upload
155
+
156
+ ```javascript
157
+ const profileCropper = Cropper(document.getElementById('profile-editor'), {
158
+ width: 200,
159
+ height: 200,
160
+ original: true
161
+ });
162
+
163
+ // After user crops the image
164
+ async function uploadProfilePicture() {
165
+ const data = profileCropper.getValue();
166
+
167
+ if (data && data[0]) {
168
+ const formData = new FormData();
169
+ const response = await fetch(data[0].content);
170
+ const blob = await response.blob();
171
+ formData.append('profile_picture', blob, 'profile.png');
172
+
173
+ // Upload to server
174
+ await fetch('/api/upload', {
175
+ method: 'POST',
176
+ body: formData
177
+ });
178
+ }
179
+ }
180
+ ```
181
+
182
+ ### E-commerce Product Image Editor
183
+
184
+ ```javascript
185
+ const productCropper = Cropper(document.getElementById('product-images'), {
186
+ width: 800,
187
+ height: 600,
188
+ original: true, // Keep original for zoom functionality
189
+ name: 'product_image'
190
+ });
191
+
192
+ // Handle multiple product images
193
+ function addProductImage(imageUrl) {
194
+ productCropper.setValue({
195
+ file: imageUrl,
196
+ original: imageUrl
197
+ });
198
+ }
199
+ ```
200
+
201
+ ### Responsive Mobile-Friendly Cropper
202
+
203
+ ```javascript
204
+ // Automatically adjusts for mobile devices
205
+ const responsiveCropper = Cropper(document.getElementById('mobile-editor'), {
206
+ width: window.innerWidth < 640 ? window.innerWidth - 40 : 600,
207
+ height: window.innerWidth < 640 ? window.innerWidth - 40 : 450
208
+ });
209
+
210
+ // The cropper automatically adapts its interface for screens < 800px
211
+ ```
212
+
213
+ ## API Reference
214
+
215
+ ### Options
216
+
217
+ | Property | Type | Default | Description |
218
+ |------------|-------------|-----------|----------------------------------------|
219
+ | `width` | number | 300 | Width of the crop area in pixels |
220
+ | `height` | number | 240 | Height of the crop area in pixels |
221
+ | `original` | boolean | false | Whether to preserve the original image |
222
+ | `name` | string | undefined | Name attribute for form integration |
223
+ | `value` | ImageData[] | null | Initial image data |
224
+
225
+ ### Methods
226
+
227
+ #### getValue()
228
+ Retrieve the current cropped image data and metadata.
229
+
230
+ ```javascript
231
+ const data = cropper.getValue();
232
+ // Returns: [{ file: 'blob:...', content: 'data:image/...', extension: 'png' }]
233
+ ```
234
+
235
+ **Returns:** `ImageData[]` - Array containing image data with file URL, content, and extension
236
+
237
+ #### setValue(data)
238
+ Set or update the image in the cropper.
239
+
240
+ ```javascript
241
+ // Set image with URL
242
+ cropper.setValue({
243
+ file: 'path/to/image.jpg',
244
+ original: 'path/to/original.jpg'
245
+ });
246
+
247
+ // Set image with string
248
+ cropper.setValue('https://example.com/image.jpg');
249
+
250
+ // Clear the image
251
+ cropper.setValue(null);
252
+ ```
253
+
254
+ **Parameters:**
255
+ - `data` (string | ImageData | ImageData[] | null): Image URL, data object, array of data objects, or null to clear
256
+
257
+ #### open()
258
+ Programmatically open the cropper modal interface.
259
+
260
+ ```javascript
261
+ cropper.open();
262
+ ```
263
+
264
+ #### uploadPhoto()
265
+ Launch the file picker modal to allow users to select a new photo.
266
+
267
+ ```javascript
268
+ // Trigger file upload dialog
269
+ document.querySelector('.upload-btn').addEventListener('click', () => {
270
+ // Note: This is handled internally by the cropper UI
271
+ });
272
+ ```
273
+
274
+ #### deletePhoto()
275
+ Remove the current image from the cropper container.
276
+
277
+ ```javascript
278
+ // Note: This method is available through the context menu and UI controls
279
+ // The cropper automatically resets when the image is deleted
280
+ ```
281
+
282
+ #### setControls(state)
283
+ Enable or disable the editing controls (zoom, rotate, brightness, contrast).
284
+
285
+ ```javascript
286
+ cropper.setControls(false); // Disable all controls
287
+ cropper.setControls(true); // Enable all controls
288
+ ```
289
+
290
+ **Parameters:**
291
+ - `state` (boolean): True to enable, false to disable
292
+
293
+ ### Properties
294
+
295
+ | Property | Type | Description |
296
+ |---------------|-------------|-----------------------------------------------|
297
+ | `brightness` | number | Current brightness adjustment value (-1 to 1) |
298
+ | `contrast` | number | Current contrast adjustment value (-1 to 1) |
299
+ | `greyscale` | number | Current greyscale value (0 to 1) |
300
+ | `cropperArea` | HTMLElement | Reference to the cropper area DOM element |
301
+
302
+ ### ImageData Interface
303
+
304
+ ```typescript
305
+ interface ImageData {
306
+ file: string; // URL or blob URL to the image
307
+ content?: string; // Base64 or data URL content
308
+ extension?: string; // File extension (e.g., 'jpg', 'png')
309
+ original?: string; // Original image URL (if preserved)
310
+ }
311
+ ```
312
+
313
+ ## Related Projects
314
+
315
+ - **[LemonadeJS](https://lemonadejs.com)** - Reactive micro JavaScript library
316
+ - **[jSuites](https://jsuites.net)** - JavaScript plugins and web components collection
317
+ - **[jSpreadsheet](https://jspreadsheet.com)** - JavaScript data grid and spreadsheet component
318
+
319
+
320
+ ## License
321
+
322
+ MIT License
323
+
324
+ ## Support & Community
325
+
326
+ - **Documentation**: [https://lemonadejs.com/plugins/cropper](https://lemonadejs.com/plugins/cropper)
327
+ - **GitHub Issues**: Report bugs and request features
328
+ - **Community**: Join our community for support and discussions
329
+ - **Website**: [https://lemonadejs.com](https://lemonadejs.com)
330
+
331
+ ## Contributing
332
+
333
+ Contributions are welcome! Please feel free to submit pull requests or open issues.
334
+
@@ -0,0 +1,119 @@
1
+ /**
2
+ * LemonadeJS Cropper Component
3
+ * Image cropper with crop, zoom, rotate, and adjustment controls
4
+ */
5
+
6
+ declare namespace Cropper {
7
+ /**
8
+ * Data structure for cropped image
9
+ */
10
+ interface ImageData {
11
+ /** URL or blob URL to the image file */
12
+ file: string;
13
+ /** Base64 or data URL content of the image */
14
+ content?: string;
15
+ /** File extension (e.g., 'jpg', 'png') */
16
+ extension?: string;
17
+ /** Original image URL (if original option is enabled) */
18
+ original?: string;
19
+ }
20
+
21
+ /**
22
+ * Configuration options for the Cropper component
23
+ */
24
+ interface Options {
25
+ /** Name attribute for the component (useful for forms) */
26
+ name?: string;
27
+
28
+ /** Initial value - image data */
29
+ value?: ImageData[] | null;
30
+
31
+ /** Width of the crop area in pixels (default: 300) */
32
+ width?: number;
33
+
34
+ /** Height of the crop area in pixels (default: 240) */
35
+ height?: number;
36
+
37
+ /** Whether to keep the original image (default: false) */
38
+ original?: boolean;
39
+ }
40
+
41
+ /**
42
+ * Cropper component instance
43
+ */
44
+ interface Instance {
45
+ /** Get the current cropped image data */
46
+ getValue(): ImageData[] | null;
47
+
48
+ /** Set or update the image data */
49
+ setValue(data: ImageData | ImageData[] | string | null): void;
50
+
51
+ /** Open the cropper modal */
52
+ open(): void;
53
+
54
+ /** Enable or disable cropper controls */
55
+ setControls(state: boolean): void;
56
+
57
+ /** Current brightness adjustment value */
58
+ brightness: number;
59
+
60
+ /** Current contrast adjustment value */
61
+ contrast: number;
62
+
63
+ /** Current greyscale adjustment value */
64
+ greyscale: number;
65
+
66
+ /** Reference to the cropper area element */
67
+ cropperArea: HTMLElement | null;
68
+
69
+ /** Name attribute */
70
+ name?: string;
71
+
72
+ /** Current value */
73
+ value: ImageData[] | null;
74
+
75
+ /** Width setting */
76
+ width?: number;
77
+
78
+ /** Height setting */
79
+ height?: number;
80
+
81
+ /** Original image setting */
82
+ original?: boolean;
83
+
84
+ /** Reference to the root DOM element */
85
+ el: HTMLElement;
86
+
87
+ /** Reference to the image container element */
88
+ image: HTMLElement;
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Create a Cropper component instance
94
+ *
95
+ * @param root - DOM element or container where the cropper will be rendered
96
+ * @param options - Configuration options for the cropper
97
+ * @returns Cropper instance
98
+ *
99
+ * @example
100
+ * ```javascript
101
+ * // Create a cropper with custom dimensions
102
+ * const cropper = Cropper(document.getElementById('container'), {
103
+ * width: 400,
104
+ * height: 300,
105
+ * original: true
106
+ * });
107
+ *
108
+ * // Set an image
109
+ * cropper.setValue({
110
+ * file: 'path/to/image.jpg'
111
+ * });
112
+ *
113
+ * // Get cropped image data
114
+ * const data = cropper.getValue();
115
+ * ```
116
+ */
117
+ declare function Cropper(root: HTMLElement, options?: Cropper.Options): Cropper.Instance;
118
+
119
+ export default Cropper;
package/dist/index.js ADDED
@@ -0,0 +1,317 @@
1
+ /**
2
+ * (c) LemonadeJS components
3
+ *
4
+ * Website: https://lemonadejs.com
5
+ * Description: Image cropper v5
6
+ *
7
+ * MIT License
8
+ *
9
+ */
10
+
11
+ if (!lemonade && typeof (require) === 'function') {
12
+ var lemonade = require('lemonadejs');
13
+ }
14
+
15
+ if (!studio && typeof (require) === 'function') {
16
+ var studio = require('@lemonadejs/studio');
17
+ }
18
+
19
+ if (typeof(cropper) === 'undefined') {
20
+ if (typeof(require) === 'function') {
21
+ var Crop = require('@jsuites/cropper');
22
+ } else if (window.cropper) {
23
+ var Crop = window.cropper;
24
+ }
25
+ } else {
26
+ var Crop = cropper;
27
+ }
28
+
29
+ ;(function (global, factory) {
30
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
31
+ typeof define === 'function' && define.amd ? define(factory) :
32
+ global.Cropper = factory();
33
+ }(this, (function () {
34
+
35
+ 'use strict';
36
+
37
+ const translate = function (t) {
38
+ if (typeof document !== 'undefined' && document.dictionary) {
39
+ return document.dictionary[t] || t;
40
+ } else {
41
+ return t;
42
+ }
43
+ };
44
+
45
+ const getWindowWidth = function() {
46
+ let w = window,
47
+ d = document,
48
+ e = d.documentElement,
49
+ g = d.getElementsByTagName('body')[0],
50
+ x = w.innerWidth || e.clientWidth || g.clientWidth;
51
+ return x;
52
+ }
53
+
54
+ const Cropper = function(children, { onload, onchange, track }) {
55
+ const self = this;
56
+ const original = self.original ? 1 : 0;
57
+ const width = self.width || 300;
58
+ const height = self.height || 240;
59
+ let crop = null;
60
+
61
+ const menu = [
62
+ {
63
+ title: translate('Change image'),
64
+ icon: 'edit',
65
+ onclick: () => {
66
+ self.open();
67
+ }
68
+ },
69
+ {
70
+ title: translate('Delete image'),
71
+ icon: 'delete',
72
+ shortcut: 'DELETE',
73
+ onclick: (e) => {
74
+ deletePhoto();
75
+ }
76
+ }
77
+ ]
78
+
79
+ self.cropperArea = null;
80
+ self.brightness = 0;
81
+ self.contrast = 0;
82
+ self.greyscale = 0;
83
+
84
+ self.createCropper = (o) => {
85
+ const area = getWindowWidth();
86
+ let a;
87
+ let c;
88
+ if (area < 800) {
89
+ a = [ area, area ];
90
+ c = [ area, area ];
91
+ } else {
92
+ a = [798, 360];
93
+ c = [width, height];
94
+ }
95
+ crop = Crop(o, {
96
+ area: a,
97
+ crop: c,
98
+ allowResize: false,
99
+ onchange: (el, image) => {
100
+ if (image) {
101
+ self.setControls(true);
102
+ }
103
+ }
104
+ });
105
+ }
106
+
107
+ const updateZoom = (e) => {
108
+ crop.zoom(e.target.value);
109
+ }
110
+
111
+ const updateRotate = (e) => {
112
+ crop.rotate(e.target.value);
113
+ }
114
+
115
+ const setBrightness = (e) => {
116
+ crop.brightness(e.target.value);
117
+ }
118
+
119
+ const setContrast = (e) => {
120
+ crop.contrast(e.target.value);
121
+ }
122
+
123
+ const updatePhoto = () => {
124
+ // Checks if cropper container is editable
125
+ if (self.cropperArea.classList.contains('jcrop_edition')) {
126
+ self.image.innerHTML = '';
127
+ // Create image with metadata
128
+ const newImage = crop.getCroppedImage();
129
+ // Callback for the blob
130
+ const createImage = (b) => {
131
+ // Transform to blob
132
+ const filename = window.URL.createObjectURL(b);
133
+ // Set upload information
134
+ const data = {
135
+ file: filename,
136
+ content: newImage.src,
137
+ extension: newImage.content.extension,
138
+ };
139
+ // Upload original image
140
+ if (original === true) {
141
+ data.original = crop.getImage().src;
142
+ }
143
+ // Update file to blob
144
+ newImage.src = filename;
145
+ // Integration with
146
+ if (self.name) {
147
+ newImage.classList.remove('jfile');
148
+ }
149
+ // Value
150
+ self.value = [data];
151
+ // Append new image
152
+ self.image.appendChild(newImage);
153
+ }
154
+ // Create image
155
+ crop.getCroppedAsBlob(createImage);
156
+ // Close the modal
157
+ setTimeout(() => {
158
+ self.modal.close();
159
+ });
160
+ }
161
+ }
162
+
163
+ const uploadPhoto = () => {
164
+ crop.getFileInput().click();
165
+ }
166
+
167
+ const deletePhoto = () => {
168
+ if (self.image) {
169
+ // Reset photo from crop
170
+ crop.reset();
171
+ // Disable controls
172
+ self.setControls(false);
173
+ // Reset from container
174
+ self.image.innerHTML = '';
175
+ // Reset container
176
+ self.value = '';
177
+ }
178
+ }
179
+
180
+ self.setControls = (state) => {
181
+ const controls = self.el.querySelectorAll('input.controls');
182
+ if (state === false) {
183
+ for (let i = 0; i < controls.length; i++) {
184
+ controls[i].setAttribute('disabled', 'disabled');
185
+ }
186
+ } else {
187
+ for (let i = 0; i < controls.length; i++) {
188
+ controls[i].removeAttribute('disabled');
189
+ }
190
+ }
191
+
192
+ for (let i = 0; i < controls.length; i++) {
193
+ if (controls[i].type === 'range') {
194
+ controls[i].value = 0;
195
+ }
196
+ }
197
+ }
198
+
199
+ self.getValue = () => {
200
+ return self.value;
201
+ }
202
+
203
+ self.setValue = (data) => {
204
+ if (!data) {
205
+ // Reset container
206
+ deletePhoto();
207
+ } else {
208
+ if (typeof(data) === 'string') {
209
+ data = {
210
+ file: data
211
+ }
212
+ }
213
+
214
+ if (data.file) {
215
+ const img = document.createElement('img');
216
+ img.setAttribute('src', data.file);
217
+ img.setAttribute('tabindex', -1);
218
+ self.image.innerHTML = '';
219
+ self.image.appendChild(img);
220
+ }
221
+
222
+ if (data.original) {
223
+ crop.addFromFile(data.original);
224
+ }
225
+
226
+ self.value = [data];
227
+ }
228
+ }
229
+
230
+ self.open = () => {
231
+ if (self.modal.isClosed()) {
232
+ self.modal.open();
233
+ }
234
+ }
235
+
236
+ onload(() => {
237
+ self.image.style.maxWidth = self.width + 'px';
238
+ self.image.style.maxHeight = self.height + 'px';
239
+
240
+ // Onclick event
241
+ self.image.addEventListener('mousedown', (e) => {
242
+ let mouseButton;
243
+ const event = e || window.event;
244
+ if (event.buttons) {
245
+ mouseButton = event.buttons;
246
+ } else if (event.button) {
247
+ mouseButton = event.button;
248
+ } else {
249
+ mouseButton = event.which;
250
+ }
251
+
252
+ if (mouseButton === 1) {
253
+ self.open();
254
+ } else {
255
+ if (event.target.tagName === 'IMG') {
256
+ event.target.focus();
257
+ }
258
+ }
259
+ });
260
+
261
+ self.el.addEventListener('onkeydown', (e) => {
262
+ if (e.key === 'Delete' && e.target.tagName === 'IMG') {
263
+ deletePhoto();
264
+ }
265
+ });
266
+
267
+ self.el.val = (v) => {
268
+ if (v === undefined) {
269
+ return self.getValue();
270
+ } else {
271
+ self.setValue(v);
272
+ }
273
+ }
274
+ });
275
+
276
+ track('value');
277
+
278
+ // Template
279
+ return render => render `<div name="{{self.name}}">
280
+ <div :ref='this.image' class="jphoto jcropper"></div>
281
+ <lm-modal draggable="true" closable="true" closed="true" width="800" height="570" title="Photo Upload" icon="photo" :ref="self.modal">
282
+ <div :ready='self.createCropper' :ref='self.cropperArea'></div>
283
+ <div class="controls">
284
+ <div style="display: flex; justify-content: center; gap: 10px; text-align: center; border-top: 1px solid #ddd;">
285
+ <label style="padding: 20px;">Zoom<br><input type='range' step='.05' min='0.1' max='5.45' value='1' oninput='${updateZoom}' class='jrange controls' disabled='disabled'></label>
286
+ <label style="padding: 20px;">Rotate<br><input type='range' step='.05' min='-1' max='1' value='0' oninput='${updateRotate}'class='jrange controls' disabled='disabled'></label>
287
+ <label style="padding: 20px;">Brigthness<br><input type='range' min='-1' max='1' step='.05' value='0' :bind='self.brightness' oninput='${setBrightness}' class='jrange controls' disabled='disabled'></label>
288
+ <label style="padding: 20px;">Contrast<br><input type='range' min='-1' max='1' step='.05' value='0' :bind='self.contrast' oninput='${setContrast}' class='jrange controls' disabled='disabled'></label>
289
+ </div>
290
+ <div class='lm-row lm-p20' style='border-top: 1px solid #ddd'>
291
+ <div class='lm-column lm-p6 lm-f1'>
292
+ <input type='button' value='Save Photo' class='lm-button controls' style='min-width: 140px;' onclick='${updatePhoto}' disabled='disabled'>
293
+ </div><div class='lm-column lm-p6'>
294
+ <input type='button' value='Upload Photo' class='lm-button' style='min-width: 140px;' onclick='${uploadPhoto}'>
295
+ </div><div class='lm-column lm-p6' style='text-align:right'>
296
+ <input type='button' value='Delete Photo' class='lm-button controls' style='min-width: 140px;' onclick='${deletePhoto}' disabled='disabled'>
297
+ </div>
298
+ </div>
299
+ </div>
300
+ </lm-modal>
301
+ <lm-contextmenu options="${menu}"></lm-contextmenu>
302
+ </div>`;
303
+ }
304
+
305
+ lemonade.setComponents({ Cropper: Cropper });
306
+ // Register the web component
307
+ lemonade.createWebComponent('cropper', Cropper);
308
+
309
+ return function (root, options, template) {
310
+ if (typeof (root) === 'object') {
311
+ lemonade.render(Cropper, root, options, template)
312
+ return options;
313
+ } else {
314
+ return Cropper.call(this, root, template)
315
+ }
316
+ }
317
+ })));
package/package.json CHANGED
@@ -1,24 +1,23 @@
1
- {
2
- "name": "@lemonadejs/cropper",
3
- "title": "JavaScript image cropper editor.",
4
- "description": "LemonadeJS and jSuites cropper editor integration",
5
- "author": {
6
- "name": "Contact <contact@lemonadejs.net>",
7
- "url": "https://lemonadejs.net"
8
- },
9
- "keywords": [
10
- "javascript cropper",
11
- "online image editor",
12
- "js",
13
- "library",
14
- "javascript plugins"
15
- ],
16
- "dependencies": {
17
- "lemonadejs": "^5.0.6",
18
- "@lemonadejs/studio": "^5.0.0",
19
- "jsuites": "^5.9.2",
20
- "@jsuites/cropper": "^1.6.2"
21
- },
22
- "main": "dist/cropper.js",
23
- "version": "5.0.0"
24
- }
1
+ {
2
+ "name": "@lemonadejs/cropper",
3
+ "title": "JavaScript image cropper editor.",
4
+ "description": "LemonadeJS and jSuites cropper editor integration",
5
+ "author": {
6
+ "name": "Contact <contact@lemonadejs.net>",
7
+ "url": "https://lemonadejs.net"
8
+ },
9
+ "keywords": [
10
+ "javascript cropper",
11
+ "online image editor",
12
+ "js",
13
+ "library",
14
+ "javascript plugins"
15
+ ],
16
+ "dependencies": {
17
+ "lemonadejs": "^5.3.2",
18
+ "@lemonadejs/studio": "^5.7.0",
19
+ "@jsuites/cropper": "^1.7.0"
20
+ },
21
+ "main": "dist/index.js",
22
+ "version": "5.1.0"
23
+ }
package/dist/cropper.js DELETED
@@ -1,370 +0,0 @@
1
- /**
2
- * (c) LemonadeJS components
3
- *
4
- * Website: https://lemonadejs.com
5
- * Description: Image cropper v5
6
- *
7
- * MIT License
8
- *
9
- */
10
-
11
- if (!lemonade && typeof (require) === 'function') {
12
- var lemonade = require('lemonadejs');
13
- }
14
-
15
- if (!studio && typeof (require) === 'function') {
16
- var studio = require('@lemonadejs/studio');
17
- }
18
-
19
- // Load jsuites
20
- if (typeof(jSuites) == 'undefined') {
21
- if (typeof(require) === 'function') {
22
- var jSuites = require('jsuites');
23
- } else if (window.jSuites) {
24
- var jSuites = window.jSuites;
25
- }
26
- }
27
-
28
- if (typeof(cropper) == 'undefined') {
29
- if (typeof(require) === 'function') {
30
- var Crop = require('@jsuites/cropper');
31
- } else if (window.cropper) {
32
- var Crop = window.cropper;
33
- }
34
- } else {
35
- var Crop = cropper;
36
- }
37
-
38
- ;(function (global, factory) {
39
- typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
40
- typeof define === 'function' && define.amd ? define(factory) :
41
- global.Cropper = factory();
42
- }(this, (function () {
43
-
44
- 'use strict';
45
-
46
- const Cropper = function() {
47
- let self = this;
48
- let original = self.original ? 1 : 0;
49
- let width = self.width || 300;
50
- let height = self.height || 240;
51
- let modal = null;
52
- let crop = null;
53
- let menu = null;
54
-
55
- self.cropperArea = null;
56
- self.brightness = 0;
57
- self.contrast = 0;
58
- self.greyscale = 0;
59
-
60
- // Methods
61
- self.createModal = function(o) {
62
- modal = jSuites.modal(o, {
63
- closed: true,
64
- width: '800px',
65
- height: '680px',
66
- title: 'Photo Upload',
67
- padding: '0',
68
- icon: 'photo'
69
- });
70
- }
71
-
72
- self.createCropper = function(o) {
73
- let area = jSuites.getWindowWidth();
74
- let a;
75
- let c;
76
- if (area < 800) {
77
- a = [ area, area ];
78
- c = [ area, area ];
79
- } else {
80
- a = [798, 360];
81
- c = [width, height];
82
- }
83
- crop = Crop(o, {
84
- area: a,
85
- crop: c ,
86
- allowResize: false,
87
- onchange: function(el, image) {
88
- if (image) {
89
- self.setControls(true);
90
- }
91
- }
92
- });
93
- }
94
-
95
- self.createMenu = function(o) {
96
- // Start contextmenu component
97
- menu = jSuites.contextmenu(o, {
98
- onclick: function(a,b) {
99
- a.close();
100
- b.stopPropagation();
101
- }
102
- });
103
- }
104
-
105
- // Controls
106
- self.createControls = function(o) {
107
- let tabs = jSuites.tabs(o.children[0], {
108
- data: [{
109
- title: 'Crop',
110
- icon: 'crop',
111
- width: '100px',
112
- },
113
- {
114
- title:'Adjusts',
115
- icon: 'image',
116
- width: '100px',
117
- }],
118
- padding:'10px',
119
- animation: true,
120
- position: 'bottom',
121
- });
122
-
123
- tabs.content.style.backgroundColor = '#eee';
124
- }
125
-
126
- const updateZoom = (e) => {
127
- crop.zoom(e.target.value);
128
- }
129
-
130
- const updateRotate = (e) => {
131
- crop.rotate(e.target.value);
132
- }
133
-
134
- const setBrightness = (e) => {
135
- crop.brightness(e.target.value);
136
- }
137
-
138
- const setContrast = (e) => {
139
- crop.contrast(e.target.value);
140
- }
141
-
142
- const updatePhoto = () => {
143
- // Checks if cropper container is editable
144
- if (self.cropperArea.classList.contains('jcrop_edition')) {
145
- self.image.innerHTML = '';
146
- // Create image with metadata
147
- let newImage = crop.getCroppedImage();
148
- // Callback for the blob
149
- let createImage = function(b) {
150
- // Transform to blob
151
- let filename = window.URL.createObjectURL(b);
152
- // Set upload information
153
- let data = {
154
- file: filename,
155
- content: newImage.src,
156
- extension: newImage.content.extension,
157
- };
158
- // Upload original image
159
- if (original == true) {
160
- data.original = crop.getImage().src;
161
- }
162
- // Update file to blob
163
- newImage.src = filename;
164
- // Integration with jSuites.form
165
- if (self.name) {
166
- newImage.classList.remove('jfile');
167
- }
168
- // Value
169
- self.value = [data];
170
- // Append new image
171
- self.image.appendChild(newImage);
172
- }
173
- // Create image
174
- crop.getCroppedAsBlob(createImage);
175
- // Close the modal
176
- setTimeout(function() {
177
- modal.close();
178
- });
179
- }
180
- }
181
-
182
- const uploadPhoto = () => {
183
- jSuites.click(crop.getFileInput());
184
- }
185
-
186
- const deletePhoto = function() {
187
- if (self.image) {
188
- // Reset photo from crop
189
- crop.reset();
190
- // Disable controls
191
- self.setControls(false);
192
- // Reset from container
193
- self.image.innerHTML = '';
194
- // Reset container
195
- self.value = null;
196
- }
197
- }
198
-
199
- self.setControls = function(state) {
200
- let controls = self.el.querySelectorAll('input.controls');
201
- if (state == false) {
202
- for (let i = 0; i < controls.length; i++) {
203
- controls[i].setAttribute('disabled', 'disabled');
204
- }
205
- } else {
206
- for (let i = 0; i < controls.length; i++) {
207
- controls[i].removeAttribute('disabled');
208
- }
209
- }
210
-
211
- for (let i = 0; i < controls.length; i++) {
212
- if (controls[i].type === 'range') {
213
- controls[i].value = 0;
214
- }
215
- }
216
- }
217
-
218
- self.getValue = function() {
219
- return self.value;
220
- }
221
-
222
- self.setValue = function(data) {
223
- if (! data) {
224
- // Reset container
225
- deletePhoto();
226
- } else {
227
- if (typeof(data) == 'string') {
228
- data = {
229
- file: data
230
- }
231
- }
232
-
233
- if (data.file) {
234
- let img = document.createElement('img');
235
- img.setAttribute('src', data.file);
236
- img.setAttribute('tabindex', -1);
237
- self.image.innerHTML = '';
238
- self.image.appendChild(img);
239
- }
240
-
241
- if (data.original) {
242
- crop.addFromFile(data.original);
243
- }
244
-
245
- self.value = [data];
246
- }
247
- }
248
-
249
- self.open = function() {
250
- if (! modal.isOpen()) {
251
- modal.open();
252
- }
253
- }
254
-
255
- self.onload = function() {
256
- self.image.style.maxWidth = self.width + 'px';
257
- self.image.style.maxHeight = self.height + 'px';
258
-
259
- // Onclick event
260
- self.el.addEventListener('mousedown', function(e) {
261
- let mouseButton;
262
- e = e || window.event;
263
- if (e.buttons) {
264
- mouseButton = e.buttons;
265
- } else if (e.button) {
266
- mouseButton = e.button;
267
- } else {
268
- mouseButton = e.which;
269
- }
270
-
271
- if (mouseButton == 1) {
272
- self.open();
273
- } else {
274
- if (e.target.tagName == 'IMG') {
275
- e.target.focus();
276
- }
277
- }
278
- });
279
-
280
- self.el.addEventListener('contextmenu', function(e) {
281
- if (e.target.tagName == 'IMG') {
282
- menu.open(e, [
283
- {
284
- title: jSuites.translate('Change image'),
285
- icon: 'edit',
286
- onclick: function() {
287
- self.open();
288
- }
289
- },
290
- {
291
- title: jSuites.translate('Delete image'),
292
- icon: 'delete',
293
- shortcut: 'DELETE',
294
- onclick: function() {
295
- e.target.remove();
296
- }
297
- }
298
- ]);
299
- e.preventDefault();
300
- }
301
- });
302
-
303
- self.el.addEventListener('onkeydown', function(e) {
304
- if (e.key == 'Delete' && e.target.tagName == 'IMG') {
305
- deletePhoto();
306
- }
307
- })
308
-
309
- self.el.val = function(v) {
310
- if (v === undefined) {
311
- return self.getValue();
312
- } else {
313
- self.setValue(v);
314
- }
315
- }
316
-
317
- }
318
-
319
- // Template
320
- return render => render `<div name="{{self.name}}" value="{{self.value}}">
321
- <div :ref='this.image' class="jphoto jcropper"></div>
322
- <div :ready='self.createModal'>
323
- <div :ready='self.createCropper' :ref='self.cropperArea'></div>
324
- <div :ready='self.createControls' class="controls">
325
- <div role='tabs'>
326
- <div role='headers'>
327
- <div style="background-color: white; padding: 15px !important;"></div>
328
- <div style="background-color: white;"></div>
329
- </div>
330
- <div role='content' style='background-color: #ccc;'>
331
- <div>
332
- <div class="lm-center lm-row">
333
- <label class="lm-f1 lm-p6"> Zoom <input type='range' step='.05' min='0.1' max='5.45' value='1' oninput='${updateZoom}' style="margin-top:10px;" class='jrange controls' disabled='disabled'></label>
334
- <label class="lm-f1 lm-p6"> Rotate <input type='range' step='.05' min='-1' max='1' value='0' oninput='${updateRotate}' style="margin-top:10px;" class='jrange controls' disabled='disabled'></label>
335
- </div>
336
- </div>
337
- <div>
338
- <div class="lm-center lm-row">
339
- <label class="lm-f1 lm-p6"> Brigthness <input type='range' min='-1' max='1' step='.05' value='0' :bind='self.brightness' oninput='${setBrightness}' style="margin-top:10px;" class='jrange controls' disabled='disabled'></label>
340
- <label class="lm-f1 lm-p6"> Contrast <input type='range' min='-1' max='1' step='.05' value='0' :bind='self.contrast' oninput='${setContrast}' style="margin-top:10px;" class='jrange controls' disabled='disabled'></label>
341
- </div>
342
- </div>
343
- </div>
344
- </div>
345
- <div class='lm-row om-p20' style='border-top: 1px solid #aaa'>
346
- <div class='lm-column lm-p6 lm-f1'>
347
- <input type='button' value='Save Photo' class='lm-button controls' style='min-width: 140px;' onclick='${updatePhoto}' disabled='disabled'>
348
- </div><div class='lm-column lm-p6'>
349
- <input type='button' value='Upload Photo' class='lm-button' style='min-width: 140px;' onclick='${uploadPhoto}'>
350
- </div><div class='lm-column lm-p6' style='text-align:right'>
351
- <input type='button' value='Delete Photo' class='lm-button controls' style='min-width: 140px;' onclick='${deletePhoto}' disabled='disabled'>
352
- </div>
353
- </div>
354
- </div>
355
- </div>
356
- <div :ready="self.createMenu"></div>
357
- </div>`;
358
- }
359
-
360
- lemonade.setComponents({ Cropper: Cropper });
361
-
362
- return function (root, options, template) {
363
- if (typeof (root) === 'object') {
364
- lemonade.render(Cropper, root, options, template)
365
- return options;
366
- } else {
367
- return Cropper.call(this, root, template)
368
- }
369
- }
370
- })));