@lemonadejs/cropper 5.0.1 → 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 CHANGED
@@ -16,16 +16,7 @@ if (!studio && typeof (require) === 'function') {
16
16
  var studio = require('@lemonadejs/studio');
17
17
  }
18
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') {
19
+ if (typeof(cropper) === 'undefined') {
29
20
  if (typeof(require) === 'function') {
30
21
  var Crop = require('@jsuites/cropper');
31
22
  } else if (window.cropper) {
@@ -43,34 +34,55 @@ if (typeof(cropper) == 'undefined') {
43
34
 
44
35
  'use strict';
45
36
 
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;
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;
52
59
  let crop = null;
53
- let menu = 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
+ ]
54
78
 
55
79
  self.cropperArea = null;
56
80
  self.brightness = 0;
57
81
  self.contrast = 0;
58
82
  self.greyscale = 0;
59
83
 
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();
84
+ self.createCropper = (o) => {
85
+ const area = getWindowWidth();
74
86
  let a;
75
87
  let c;
76
88
  if (area < 800) {
@@ -82,9 +94,9 @@ if (typeof(cropper) == 'undefined') {
82
94
  }
83
95
  crop = Crop(o, {
84
96
  area: a,
85
- crop: c ,
97
+ crop: c,
86
98
  allowResize: false,
87
- onchange: function(el, image) {
99
+ onchange: (el, image) => {
88
100
  if (image) {
89
101
  self.setControls(true);
90
102
  }
@@ -92,37 +104,6 @@ if (typeof(cropper) == 'undefined') {
92
104
  });
93
105
  }
94
106
 
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
107
  const updateZoom = (e) => {
127
108
  crop.zoom(e.target.value);
128
109
  }
@@ -144,24 +125,24 @@ if (typeof(cropper) == 'undefined') {
144
125
  if (self.cropperArea.classList.contains('jcrop_edition')) {
145
126
  self.image.innerHTML = '';
146
127
  // Create image with metadata
147
- let newImage = crop.getCroppedImage();
128
+ const newImage = crop.getCroppedImage();
148
129
  // Callback for the blob
149
- let createImage = function(b) {
130
+ const createImage = (b) => {
150
131
  // Transform to blob
151
- let filename = window.URL.createObjectURL(b);
132
+ const filename = window.URL.createObjectURL(b);
152
133
  // Set upload information
153
- let data = {
134
+ const data = {
154
135
  file: filename,
155
136
  content: newImage.src,
156
137
  extension: newImage.content.extension,
157
138
  };
158
139
  // Upload original image
159
- if (original == true) {
140
+ if (original === true) {
160
141
  data.original = crop.getImage().src;
161
142
  }
162
143
  // Update file to blob
163
144
  newImage.src = filename;
164
- // Integration with jSuites.form
145
+ // Integration with
165
146
  if (self.name) {
166
147
  newImage.classList.remove('jfile');
167
148
  }
@@ -173,17 +154,17 @@ if (typeof(cropper) == 'undefined') {
173
154
  // Create image
174
155
  crop.getCroppedAsBlob(createImage);
175
156
  // Close the modal
176
- setTimeout(function() {
177
- modal.close();
157
+ setTimeout(() => {
158
+ self.modal.close();
178
159
  });
179
160
  }
180
161
  }
181
162
 
182
163
  const uploadPhoto = () => {
183
- jSuites.click(crop.getFileInput());
164
+ crop.getFileInput().click();
184
165
  }
185
166
 
186
- const deletePhoto = function() {
167
+ const deletePhoto = () => {
187
168
  if (self.image) {
188
169
  // Reset photo from crop
189
170
  crop.reset();
@@ -192,13 +173,13 @@ if (typeof(cropper) == 'undefined') {
192
173
  // Reset from container
193
174
  self.image.innerHTML = '';
194
175
  // Reset container
195
- self.value = null;
176
+ self.value = '';
196
177
  }
197
178
  }
198
179
 
199
- self.setControls = function(state) {
200
- let controls = self.el.querySelectorAll('input.controls');
201
- if (state == false) {
180
+ self.setControls = (state) => {
181
+ const controls = self.el.querySelectorAll('input.controls');
182
+ if (state === false) {
202
183
  for (let i = 0; i < controls.length; i++) {
203
184
  controls[i].setAttribute('disabled', 'disabled');
204
185
  }
@@ -215,23 +196,23 @@ if (typeof(cropper) == 'undefined') {
215
196
  }
216
197
  }
217
198
 
218
- self.getValue = function() {
199
+ self.getValue = () => {
219
200
  return self.value;
220
201
  }
221
202
 
222
- self.setValue = function(data) {
223
- if (! data) {
203
+ self.setValue = (data) => {
204
+ if (!data) {
224
205
  // Reset container
225
206
  deletePhoto();
226
207
  } else {
227
- if (typeof(data) == 'string') {
208
+ if (typeof(data) === 'string') {
228
209
  data = {
229
210
  file: data
230
211
  }
231
212
  }
232
213
 
233
214
  if (data.file) {
234
- let img = document.createElement('img');
215
+ const img = document.createElement('img');
235
216
  img.setAttribute('src', data.file);
236
217
  img.setAttribute('tabindex', -1);
237
218
  self.image.innerHTML = '';
@@ -246,103 +227,67 @@ if (typeof(cropper) == 'undefined') {
246
227
  }
247
228
  }
248
229
 
249
- self.open = function() {
250
- if (! modal.isOpen()) {
251
- modal.open();
230
+ self.open = () => {
231
+ if (self.modal.isClosed()) {
232
+ self.modal.open();
252
233
  }
253
234
  }
254
235
 
255
- self.onload = function() {
236
+ onload(() => {
256
237
  self.image.style.maxWidth = self.width + 'px';
257
238
  self.image.style.maxHeight = self.height + 'px';
258
239
 
259
240
  // Onclick event
260
- self.el.addEventListener('mousedown', function(e) {
241
+ self.image.addEventListener('mousedown', (e) => {
261
242
  let mouseButton;
262
- e = e || window.event;
263
- if (e.buttons) {
264
- mouseButton = e.buttons;
265
- } else if (e.button) {
266
- mouseButton = e.button;
243
+ const event = e || window.event;
244
+ if (event.buttons) {
245
+ mouseButton = event.buttons;
246
+ } else if (event.button) {
247
+ mouseButton = event.button;
267
248
  } else {
268
- mouseButton = e.which;
249
+ mouseButton = event.which;
269
250
  }
270
251
 
271
- if (mouseButton == 1) {
252
+ if (mouseButton === 1) {
272
253
  self.open();
273
254
  } else {
274
- if (e.target.tagName == 'IMG') {
275
- e.target.focus();
255
+ if (event.target.tagName === 'IMG') {
256
+ event.target.focus();
276
257
  }
277
258
  }
278
259
  });
279
260
 
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') {
261
+ self.el.addEventListener('onkeydown', (e) => {
262
+ if (e.key === 'Delete' && e.target.tagName === 'IMG') {
305
263
  deletePhoto();
306
264
  }
307
- })
265
+ });
308
266
 
309
- self.el.val = function(v) {
267
+ self.el.val = (v) => {
310
268
  if (v === undefined) {
311
269
  return self.getValue();
312
270
  } else {
313
271
  self.setValue(v);
314
272
  }
315
273
  }
274
+ });
316
275
 
317
- }
276
+ track('value');
318
277
 
319
278
  // Template
320
- return render => render `<div name="{{self.name}}" value="{{self.value}}">
279
+ return render => render `<div name="{{self.name}}">
321
280
  <div :ref='this.image' class="jphoto jcropper"></div>
322
- <div :ready='self.createModal'>
281
+ <lm-modal draggable="true" closable="true" closed="true" width="800" height="570" title="Photo Upload" icon="photo" :ref="self.modal">
323
282
  <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>
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>
344
289
  </div>
345
- <div class='lm-row om-p20' style='border-top: 1px solid #aaa'>
290
+ <div class='lm-row lm-p20' style='border-top: 1px solid #ddd'>
346
291
  <div class='lm-column lm-p6 lm-f1'>
347
292
  <input type='button' value='Save Photo' class='lm-button controls' style='min-width: 140px;' onclick='${updatePhoto}' disabled='disabled'>
348
293
  </div><div class='lm-column lm-p6'>
@@ -352,12 +297,14 @@ if (typeof(cropper) == 'undefined') {
352
297
  </div>
353
298
  </div>
354
299
  </div>
355
- </div>
356
- <div :ready="self.createMenu"></div>
300
+ </lm-modal>
301
+ <lm-contextmenu options="${menu}"></lm-contextmenu>
357
302
  </div>`;
358
303
  }
359
304
 
360
305
  lemonade.setComponents({ Cropper: Cropper });
306
+ // Register the web component
307
+ lemonade.createWebComponent('cropper', Cropper);
361
308
 
362
309
  return function (root, options, template) {
363
310
  if (typeof (root) === 'object') {
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/index.js",
23
- "version": "5.0.1"
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
+ }