@lemonadejs/cropper 5.0.1 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1 +1,335 @@
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` - Object 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 | 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
+ guid?: string;
311
+ }
312
+ ```
313
+
314
+ ## Related Projects
315
+
316
+ - **[LemonadeJS](https://lemonadejs.com)** - Reactive micro JavaScript library
317
+ - **[Jspreadsheet](https://jspreadsheet.com)** - JavaScript data grid and spreadsheet component
318
+ - **[CalendarJS](https://calendarjs.com)** - JavaScript calendar, schedule and timeline components
319
+ - **[jSuites](https://jsuites.net)** - JavaScript plugins and web components collection
320
+
321
+ ## License
322
+
323
+ MIT License
324
+
325
+ ## Support & Community
326
+
327
+ - **Documentation**: [https://lemonadejs.com/plugins/cropper](https://lemonadejs.com/plugins/cropper)
328
+ - **GitHub Issues**: Report bugs and request features
329
+ - **Community**: Join our community for support and discussions
330
+ - **Website**: [https://lemonadejs.com](https://lemonadejs.com)
331
+
332
+ ## Contributing
333
+
334
+ Contributions are welcome! Please feel free to submit pull requests or open issues.
335
+
@@ -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
  }
@@ -142,63 +123,52 @@ if (typeof(cropper) == 'undefined') {
142
123
  const updatePhoto = () => {
143
124
  // Checks if cropper container is editable
144
125
  if (self.cropperArea.classList.contains('jcrop_edition')) {
145
- self.image.innerHTML = '';
146
126
  // Create image with metadata
147
- let newImage = crop.getCroppedImage();
127
+ const newImage = crop.getCroppedImage();
148
128
  // Callback for the blob
149
- let createImage = function(b) {
129
+ const createImage = (b) => {
150
130
  // Transform to blob
151
- let filename = window.URL.createObjectURL(b);
131
+ const filename = window.URL.createObjectURL(b);
152
132
  // Set upload information
153
- let data = {
133
+ const data = {
154
134
  file: filename,
155
135
  content: newImage.src,
156
136
  extension: newImage.content.extension,
157
137
  };
158
138
  // Upload original image
159
- if (original == true) {
139
+ if (original === true) {
160
140
  data.original = crop.getImage().src;
161
141
  }
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
142
  // Value
169
- self.value = [data];
170
- // Append new image
171
- self.image.appendChild(newImage);
143
+ self.value = data;
172
144
  }
173
145
  // Create image
174
146
  crop.getCroppedAsBlob(createImage);
175
147
  // Close the modal
176
- setTimeout(function() {
177
- modal.close();
148
+ setTimeout(() => {
149
+ self.modal.close();
178
150
  });
179
151
  }
180
152
  }
181
153
 
182
154
  const uploadPhoto = () => {
183
- jSuites.click(crop.getFileInput());
155
+ crop.getFileInput().click();
184
156
  }
185
157
 
186
- const deletePhoto = function() {
158
+ const deletePhoto = () => {
187
159
  if (self.image) {
188
160
  // Reset photo from crop
189
161
  crop.reset();
190
162
  // Disable controls
191
163
  self.setControls(false);
192
- // Reset from container
193
- self.image.innerHTML = '';
194
164
  // Reset container
195
- self.value = null;
165
+ self.value = '';
196
166
  }
197
167
  }
198
168
 
199
- self.setControls = function(state) {
200
- let controls = self.el.querySelectorAll('input.controls');
201
- if (state == false) {
169
+ self.setControls = (state) => {
170
+ const controls = self.el.querySelectorAll('input.controls');
171
+ if (state === false) {
202
172
  for (let i = 0; i < controls.length; i++) {
203
173
  controls[i].setAttribute('disabled', 'disabled');
204
174
  }
@@ -215,149 +185,142 @@ if (typeof(cropper) == 'undefined') {
215
185
  }
216
186
  }
217
187
 
218
- self.getValue = function() {
188
+ self.getValue = () => {
219
189
  return self.value;
220
190
  }
221
191
 
222
- self.setValue = function(data) {
223
- if (! data) {
192
+ self.setValue = (data) => {
193
+ if (!data) {
224
194
  // Reset container
225
195
  deletePhoto();
226
196
  } else {
227
- if (typeof(data) == 'string') {
197
+ if (typeof(data) === 'string') {
228
198
  data = {
229
199
  file: data
230
200
  }
231
201
  }
232
202
 
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
203
  if (data.original) {
242
204
  crop.addFromFile(data.original);
243
205
  }
244
206
 
245
- self.value = [data];
207
+ self.value = data;
246
208
  }
247
209
  }
248
210
 
249
- self.open = function() {
250
- if (! modal.isOpen()) {
251
- modal.open();
211
+ self.open = () => {
212
+ if (self.modal.isClosed()) {
213
+ self.modal.open();
252
214
  }
253
215
  }
254
216
 
255
- self.onload = function() {
217
+ onload(() => {
256
218
  self.image.style.maxWidth = self.width + 'px';
257
219
  self.image.style.maxHeight = self.height + 'px';
258
220
 
259
221
  // Onclick event
260
- self.el.addEventListener('mousedown', function(e) {
222
+ self.image.addEventListener('mousedown', (e) => {
261
223
  let mouseButton;
262
- e = e || window.event;
263
- if (e.buttons) {
264
- mouseButton = e.buttons;
265
- } else if (e.button) {
266
- mouseButton = e.button;
224
+ const event = e || window.event;
225
+ if (event.buttons) {
226
+ mouseButton = event.buttons;
227
+ } else if (event.button) {
228
+ mouseButton = event.button;
267
229
  } else {
268
- mouseButton = e.which;
230
+ mouseButton = event.which;
269
231
  }
270
232
 
271
- if (mouseButton == 1) {
233
+ if (mouseButton === 1) {
272
234
  self.open();
273
235
  } else {
274
- if (e.target.tagName == 'IMG') {
275
- e.target.focus();
236
+ if (event.target.tagName === 'IMG') {
237
+ event.target.focus();
276
238
  }
277
239
  }
278
240
  });
279
241
 
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') {
242
+ self.el.addEventListener('onkeydown', (e) => {
243
+ if (e.key === 'Delete' && e.target.tagName === 'IMG') {
305
244
  deletePhoto();
306
245
  }
307
- })
246
+ });
308
247
 
309
- self.el.val = function(v) {
248
+ self.el.val = (v) => {
310
249
  if (v === undefined) {
311
250
  return self.getValue();
312
251
  } else {
313
252
  self.setValue(v);
314
253
  }
315
254
  }
255
+ });
316
256
 
317
- }
257
+ onchange((prop) => {
258
+ let image = self.value;
259
+ let filename = null;
260
+
261
+ if (Array.isArray(self.value)) {
262
+ image = self.value[0];
263
+ }
264
+ const img = document.createElement('img');
265
+ img.setAttribute('tabindex', -1);
266
+ self.image.textContent = '';
267
+ // Integration with
268
+ if (self.name) {
269
+ img.classList.remove('jfile');
270
+ }
271
+
272
+ if (image) {
273
+ if (typeof image === 'string') {
274
+ filename = image;
275
+ } else if (typeof image === 'object') {
276
+ filename = image.file;
277
+ }
278
+ }
279
+
280
+ if (filename) {
281
+ if (typeof self.onbeforeloadimage === 'function') {
282
+ let ret = self.onbeforeloadimage(self, filename, img);
283
+ if (ret) {
284
+ filename = ret;
285
+ }
286
+ }
287
+ img.setAttribute('src', filename);
288
+ self.image.appendChild(img);
289
+ }
290
+ })
291
+
292
+ track('value');
318
293
 
319
294
  // Template
320
- return render => render `<div name="{{self.name}}" value="{{self.value}}">
295
+ return render => render `<div name="{{self.name}}">
321
296
  <div :ref='this.image' class="jphoto jcropper"></div>
322
- <div :ready='self.createModal'>
297
+ <lm-modal draggable="true" closable="true" closed="true" width="800" height="580" title="Photo Upload" icon="photo" :ref="self.modal">
323
298
  <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>
299
+ <div class="controls">
300
+ <div style="display: flex; justify-content: center; gap: 10px; text-align: center; border-top: 1px solid #ddd;">
301
+ <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>
302
+ <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>
303
+ <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>
304
+ <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
305
  </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'>
306
+ <div class='lm-row lm-p15' style='border-top: 1px solid #ddd'>
307
+ <div class='lm-column lm-f1'>
308
+ <input type='button' value='Save Photo' class='lm-button controls' style='width: 100%' onclick='${updatePhoto}' disabled='disabled'>
309
+ </div><div class='lm-column'>
310
+ <input type='button' value='Upload Photo' class='lm-button' style='width: 100%' onclick='${uploadPhoto}'>
311
+ </div><div class='lm-column' style='text-align:right'>
312
+ <input type='button' value='Delete Photo' class='lm-button controls' style='width: 100%' onclick='${deletePhoto}' disabled='disabled'>
352
313
  </div>
353
314
  </div>
354
315
  </div>
355
- </div>
356
- <div :ready="self.createMenu"></div>
316
+ </lm-modal>
317
+ <lm-contextmenu options="${menu}"></lm-contextmenu>
357
318
  </div>`;
358
319
  }
359
320
 
360
321
  lemonade.setComponents({ Cropper: Cropper });
322
+ // Register the web component
323
+ lemonade.createWebComponent('cropper', Cropper);
361
324
 
362
325
  return function (root, options, template) {
363
326
  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.3",
18
+ "@lemonadejs/studio": "^5.7.11",
19
+ "@jsuites/cropper": "^1.7.0"
20
+ },
21
+ "main": "dist/index.js",
22
+ "version": "6.0.0"
23
+ }