Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
/**
2
 * lc_color_picker.js - The colorpicker for modern web
3
 * Version: 2.0.0
4
 * Author: Luca Montanari (LCweb)
5
 * Website: https://lcweb.it
6
 * Licensed under the MIT license
7
 */
8
 
9
 
10
(function (global, factory) {
11
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
12
    typeof define === 'function' && define.amd ? define(factory) :
13
    (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Chart = factory());
14
    })(this, (function () { 'use strict';
15
 
16
    if(typeof(window.lc_color_picker) != 'undefined') {return false;} // prevent multiple script inits
17
 
18
 
19
    /*** vars ***/
20
    let debounced_vars  = [],
21
        window_width   = null,
22
 
23
        style_generated = null,
24
        active_trigger  = null,
25
        active_trig_id  = null,
26
 
27
        active_solid    = null,
28
        active_opacity  = null,
29
        active_gradient = null,
30
        active_mode     = 'linear-gradient',
31
 
32
        sel_grad_step   = 0, // selected gradient step
33
        gradient_data   = {
34
            deg: 0,
35
            radial_circle: false,
36
            steps: [
37
                //{color : null, opacity: null, position : null}
38
            ],
39
        };
40
 
41
 
42
    /*** default options ***/
43
    const def_opts = {
44
        modes           : ['linear-gradient'], // (array) containing supported modes (solid | linear-gradient | radial-gradient)
45
        open_on_focus   : true, // (bool) whether to open the picker when field is focused
46
        transparency    : true, // (bool) whether to allow colors transparency tune
47
        dark_theme      : false, // (bool) whether to enable dark picker theme
48
        no_input_mode   : false, // (bool) whether to stretch the trigger in order to cover the whole input field
49
        wrap_width      : 'auto', // (string) defines the wrapper width. "auto" to leave it up to CSS, "inherit" to statically copy input field width, or any other CSS sizing
50
        preview_style   : { // (object) defining shape and position of the in-field preview
51
            input_padding   : 35, // extra px padding eventually added to the target input to not cover text
52
            side            : 'right', // right or left
53
            width           : 30,
54
            separator_color : '#ccc', // (string) CSS color applird to preview element as separator
55
        },
56
        fallback_colors : ['#008080', 'linear-gradient(90deg, #fff 0%, #000 100%)'], // (array) defining default colors used when trigger field has no value. First parameter for solid color, second for gradient
57
 
58
        on_change       : null, // function(new_value, target_field) {}, - triggered every time field value changes. Passes value and target field object as parameters
59
 
60
        labels          : [ // (array) option used to translate script texts
61
            'click to change color',
62
            'Solid',
63
            'Linear Gradient',
64
            'Radial Gradient',
65
            'add gradient step',
66
            'gradient angle',
67
            'gradient shape',
68
            'color',
69
            'opacity',
70
        ],
71
    };
72
 
73
 
74
    // shortcut var to target the text input only
75
    const right_input_selector = 'input:not([type="color"])';
76
 
77
 
78
 
79
    // input value check custom event
80
    const lccp_ivc_event = function(picker_id, hide_picker = false) {
81
        return new CustomEvent('lccp_input_val_check', {
82
            bubbles : true,
83
            detail: {
84
                picker_id   : picker_id,
85
                hide_picker : hide_picker
86
            }
87
        });
88
    };
89
 
90
 
91
 
92
    /*** hide picker cicking outside ***/
93
    document.addEventListener('click', function(e) {
94
        const picker = document.querySelector("#lc-color-picker.lccp-shown");
95
        if(!picker || e.target.classList.contains('lccp-preview')) {
96
            return true;
97
        }
98
 
99
        // is an element within a trigger?
100
        for (const trigger of document.getElementsByClassName('lccp-preview')) {
101
            if(trigger.contains(e.target)) {
102
                return true;
103
            }
104
        }
105
 
106
        // clicked on the same colorpicker field? keep visible
107
        if(e.target.parentNode && e.target.parentNode.classList && e.target.parentNode.classList.contains('lccp-el-wrap') && document.getElementById(active_trig_id)) {
108
            return true;
109
        }
110
 
111
        // close if clicked element is not in the picker
112
        if(!picker.contains(e.target) && !e.target.classList.contains('lccp-shown')) {
113
            const picker_id = picker.getAttribute('data-trigger-id'),
114
            $input = document.getElementById(picker_id).parentNode.querySelector(right_input_selector);
115
 
116
            $input.dispatchEvent(lccp_ivc_event(picker_id, true));
117
        }
118
        return true;
119
    });
120
 
121
 
122
    /* hide picker on screen resizing */
123
    window.addEventListener('resize', function(e) {
124
        const picker = document.querySelector("#lc-color-picker.lccp-shown");
125
        if(!picker || window_width == window.innerWidth) {
126
            return true;
127
        }
128
 
129
        // check field value
130
        const picker_id = picker.getAttribute('data-trigger-id'),
131
              $input = document.getElementById(picker_id).parentNode.querySelector(right_input_selector);
132
 
133
        $input.dispatchEvent(lccp_ivc_event(picker_id, true));
134
    });
135
 
136
 
137
    /* extend string object to ReplaceArray */
138
    String.prototype.lccpReplaceArray = function(find, replace) {
139
        let replaceString = this;
140
        let regex;
141
 
142
        for (var i = 0; i < find.length; i++) {
143
            const regex = new RegExp(find[i], "g");
144
            replaceString = (typeof(replace) == 'object') ? replaceString.replace(regex, replace[i]) : replaceString.replace(regex, replace);
145
        }
146
        return replaceString;
147
    };
148
 
149
 
150
 
151
 
152
    /*** plugin class ***/
153
    window.lc_color_picker = function(attachTo, options = {}) {
154
        let cp_uniqid, // unique ID assigned to this colorpicker instance
155
            last_tracked_col;
156
 
157
        this.attachTo = attachTo;
158
        if(!this.attachTo) {
159
            return console.error('You must provide a valid selector string first argument');
160
        }
161
 
162
        // override options
163
        if(typeof(options) !=  'object') {
164
            return console.error('Options must be an object');
165
        }
166
 
167
        const bkp_opts = options;
168
        options = Object.assign({}, def_opts, options);
169
 
170
        if(typeof(bkp_opts.preview_style) != 'undefined') {
171
            options.preview_style = Object.assign({}, def_opts.preview_style, bkp_opts.preview_style);
172
        }
173
 
174
 
175
 
176
        /* initialize */
177
        this.init = function() {
178
            const $this = this;
179
 
180
            // Generate style
181
            if(!style_generated) {
182
                this.generate_style();
183
                style_generated = true;
184
            }
185
 
186
 
187
            // assign to each target element
188
            maybe_querySelectorAll(attachTo).forEach(function(el) {
189
                if(el.tagName == 'INPUT' && el.getAttribute('type') != 'text') {
190
                    return;
191
                }
192
 
193
                // do not initialize twice
194
                if(el.parentNode.classList.length && el.parentNode.classList.contains('lcslt_wrap')) {
195
                    return;
196
                }
197
 
198
                $this.wrap_element(el);
199
            });
200
        };
201
 
202
 
203
 
204
        /* wrap target element to allow trigger display */
205
        this.wrap_element = function(el) {
206
            cp_uniqid = Math.random().toString(36).substr(2, 9);
207
 
208
            const $this     = this,
209
                  side_prop = (options.preview_style.side == 'right') ? 'borderRightWidth' : 'borderLeftWidth';
210
 
211
            let trigger_css =
212
                `width:${ (options.no_input_mode) ? 'calc(100% - '+ parseInt(getComputedStyle(el)['borderRightWidth'], 10) +'px - '+ parseInt(getComputedStyle(el)['borderLeftWidth'], 10) +'px);' : options.preview_style.width +'px;'}` +
213
 
214
                options.preview_style.side +':'+ parseInt(getComputedStyle(el)[side_prop], 10) +'px;'+
215
 
216
                'top:'+ parseInt(getComputedStyle(el)['borderTopWidth'], 10) +'px;' +
217
 
218
                'height: calc(100% - '+ parseInt(getComputedStyle(el)['borderTopWidth'], 10) +'px - '+ parseInt(getComputedStyle(el)['borderBottomWidth'], 10) +'px);';
219
 
220
            let trigger_upper_css =
221
                trigger_css +
222
                'background:'+ el.value +';' +
223
                'border-color:'+ options.preview_style.separator_color +';'
224
 
225
            let div = document.createElement('div');
226
            div.className = 'lccp-preview-'+ options.preview_style.side;
227
            div.setAttribute('data-for', el.getAttribute('name'));
228
 
229
            // static width from input?
230
            if(options.wrap_width != 'auto') {
231
                div.style.width = (options.wrap_width == 'inherit') ? Math.round(el.getBoundingClientRect().width) + 'px' : options.wrap_width;
232
            }
233
 
234
            const direct_colorpicker_code = (!options.transparency && options.modes.length == 1 && options.modes[0] == 'linear-gradient') ?
235
                '<input type="color" name="'+ cp_uniqid +'_direct_cp" value="'+ el.value +'" class="lccp-direct-cp-f" />' : '';
236
 
237
            div.classList.add("lccp-el-wrap");
238
            div.innerHTML =
239
                '<span class="lccp-preview-bg" style="'+ trigger_css +'"></span>' +
240
                '<span id="'+ cp_uniqid +'" class="lccp-preview" style="'+ trigger_upper_css +'" title="'+ options.labels[0] +'"></span>' +
241
                direct_colorpicker_code;
242
 
243
            el.parentNode.insertBefore(div, el);
244
            div.appendChild(el);
245
 
246
            // input padding
247
            if(!options.no_input_mode) {
248
                if(options.preview_style.side == 'right') {
249
                    div.querySelector('input:not([type="color"])').style.paddingRight = options.preview_style.input_padding +'px';
250
                } else {
251
                    div.querySelector('input:not([type="color"])').style.paddingLeft = options.preview_style.input_padding +'px';
252
                }
253
            }
254
 
255
 
256
            // direct browser colorpicker? track changes
257
            if(div.querySelector('.lccp-direct-cp-f')) {
258
                div.querySelector('.lccp-direct-cp-f').addEventListener("input", (e) => {
259
 
260
                    div.querySelector('input:not([type="color"])').value = e.target.value;
261
                    div.querySelector('.lccp-preview').style.background = e.target.value;
262
                });
263
            }
264
 
265
 
266
            // event to show picker
267
            const trigger = document.getElementById(cp_uniqid);
268
            trigger.addEventListener("click", (e) => {
269
                this.show_picker(trigger);
270
            });
271
 
272
 
273
 
274
            // show on field focus?
275
            if(options.open_on_focus) {
276
                div.querySelector(right_input_selector).addEventListener("focus", (e) => {
277
                    if(trigger != active_trigger) {
278
                        if(active_trigger) {
279
                            document.getElementById('lc-color-picker').classList.remove('lccp-shown');
280
                            active_trigger = null;
281
                        }
282
 
283
                        $this.debounce('open_on_focus', 10, 'show_picker', trigger);
284
                    }
285
                });
286
            }
287
 
288
 
289
            // sync manually-inputed data in the field
290
            div.querySelector(right_input_selector).addEventListener("keyup", (e) => {
291
                if(e.keyCode == 9 || e.key === 'Enter' || e.keyCode === 13) {
292
                    return;
293
                }
294
 
295
                const is_active_trigger_and_opened = (active_trig_id = cp_uniqid && document.querySelector("#lc-color-picker.lccp-shown")) ? true : false;
296
 
297
                active_trigger = trigger;
298
                active_trig_id = cp_uniqid;
299
 
300
                $this.debounce('manual_input_sync', 10, 'val_to_picker', true);
301
 
302
                if(is_active_trigger_and_opened) {
303
                    $this.debounce('manual_input_sync_cp', 10, 'append_color_picker', false);
304
                    $this.debounce('reopen_picker_after_manual_edit', 10, 'show_picker', trigger);
305
                }
306
            });
307
 
308
 
309
            // be sure input value is managed on focusout
310
            div.querySelector(right_input_selector).addEventListener("focusout", (e) => {
311
                // not if this field's picker is shown and focus is on "body"
312
                if(document.activeElement.tagName == 'BODY' && document.querySelector('#lc-color-picker.lccp-shown[data-trigger-id="'+ active_trig_id +'"]')) {
313
                    return true;
314
                }
315
 
316
                e.target.dispatchEvent(lccp_ivc_event(active_trig_id, true));
317
            });
318
 
319
 
320
            // custom event - check field validity and eventually use fallback values
321
            div.querySelector(right_input_selector).addEventListener("lccp_input_val_check", (e) => {
322
                const curr_val = e.target.value,
323
                      test = document.createElement('div');
324
 
325
                test.style.background = curr_val;
326
                let browser_val = test.style.background,
327
                    val_to_set;
328
 
329
                if(!curr_val.trim().length || !browser_val) {
330
                    if(e.target.value.toLowerCase().indexOf('gradient') === -1) {
331
                        val_to_set = (options.fallback_colors[0].toLowerCase().indexOf('rgba') === -1) ? $this.RGB_to_hex(options.fallback_colors[0]) : options.fallback_colors[0];
332
                    }
333
                    else {
334
                        val_to_set = options.fallback_colors[1];
335
                    }
336
                }
337
                else {
338
                    // browser already fixes minor things
339
                    browser_val = browser_val.replaceAll('0.', '.').replace(/rgb\([^\)]+\)/g, (rgb) => {
340
                        return $this.RGB_to_hex(rgb);
341
                    });
342
 
343
                    val_to_set = (browser_val.trim().toLowerCase().substr(0, 4) == 'rgb(') ? $this.RGB_to_hex(browser_val) : browser_val;
344
                }
345
 
346
                if(val_to_set != curr_val) {
347
                    e.target.value = val_to_set;
348
                }
349
 
350
                if(typeof(options.on_change) == 'function' && last_tracked_col != val_to_set) {
351
                    options.on_change.call($this, val_to_set, e.target);
352
                }
353
 
354
                if(e.detail.picker_id == active_trig_id) {
355
                    active_trigger = null;
356
                    active_trig_id = null;
357
                }
358
 
359
 
360
                // also hide picker?
361
                const $target = document.querySelector('#lc-color-picker.lccp-shown[data-trigger-id="'+ e.detail.picker_id +'"]');
362
                if($target) {
363
 
364
                    $target.classList.remove('lccp-shown');
365
                    document.getElementById("lc-color-picker").remove();
366
                }
367
            });
368
        };
369
 
370
 
371
 
372
        /* show picker */
373
        this.show_picker = function(trigger) {
374
            if(document.querySelector('#lc-color-picker.lccp-shown[data-trigger-id="'+ active_trig_id +'"]')) {
375
                document.getElementById("lc-color-picker").remove();
376
                active_trigger = null;
377
                active_trig_id = null
378
 
379
                return false;
380
            }
381
 
382
            // direct colorpicker usage? Not for Firefox is "show on focus" is enabled
383
            const direct_colorpicker = trigger.parentNode.querySelector('.lccp-direct-cp-f');
384
            if(
385
                direct_colorpicker &&
386
                (
387
                    !options.open_on_focus ||
388
                    (options.open_on_focus && !navigator.userAgent.toLowerCase().includes('firefox'))
389
                )
390
            ) {
391
                direct_colorpicker.value = active_solid;
392
                direct_colorpicker.click();
393
                return true;
394
            }
395
 
396
 
397
            window_width = window.innerWidth;
398
            active_trigger = trigger;
399
            active_trig_id = cp_uniqid;
400
 
401
            this.val_to_picker();
402
            this.append_color_picker();
403
 
404
            const picker      = document.getElementById('lc-color-picker'),
405
                  picker_w    = picker.offsetWidth,
406
                  picker_h    = picker.offsetHeight,
407
                  at_offsety  = active_trigger.getBoundingClientRect(),
408
                  at_h        = parseInt(active_trigger.clientHeight, 10) + parseInt(getComputedStyle(active_trigger)['borderTopWidth'], 10) + parseInt(getComputedStyle(active_trigger)['borderBottomWidth'], 10),
409
                  y_pos       = (parseInt(at_offsety.y, 10) + parseInt(window.pageYOffset, 10) + at_h + 5);
410
 
411
            // left pos control - also checking side overflows
412
            let left = (parseInt(at_offsety.right, 10) - picker_w);
413
            if(left < 0) {
414
                left = 0;
415
            }
416
 
417
            // mobile? show it centered
418
            if(window.innerWidth < 700) {
419
                left = Math.floor( (window.innerWidth - picker_w) / 2);
420
            }
421
 
422
            // top or bottom ?
423
            const y_pos_css = (y_pos + picker_h - document.documentElement.scrollTop < window.innerHeight) ?
424
                    'top:'+ y_pos :
425
                    'transform: translate3d(0, calc((100% + '+ (active_trigger.offsetHeight + 10) +'px) * -1), 0); top:'+ y_pos;
426
 
427
            picker.setAttribute('style', y_pos_css +'px; left: '+ left +'px;');
428
            picker.classList.add('lccp-shown');
429
        };
430
 
431
 
432
 
433
        /* handles input value and prepres data for the picker */
434
        this.val_to_picker = function(from_manual_input) {
435
            if(!active_trigger) {
436
                return false;
437
            }
438
            const val = active_trigger.parentNode.querySelector(right_input_selector).value.trim().toLowerCase();
439
            last_tracked_col = val;
440
 
441
            // check validity
442
            let test = document.createElement('div');
443
            test.style.background = val;
444
 
445
            //// set active colors
446
            // if no value found
447
            if(!val.length || !test.style.background.length) {
448
                active_solid = options.fallback_colors[0];
449
                active_gradient = options.fallback_colors[1];
450
                active_mode = 'linear-gradient';
451
 
452
                /* if(val.indexOf('linear-gradient') !== -1) {
453
                }
454
                else if(val.indexOf('radial-gradient') !== -1) {
455
                    active_mode = 'radial-gradient';
456
                }
457
                else {
458
                    active_mode = 'solid';
459
                } */
460
            }
461
            else {
462
 
463
                active_mode = 'linear-gradient';
464
                active_gradient = val;
465
                // find which value type has been passed
466
                /* if(val.indexOf('linear-gradient') !== -1) {
467
                }
468
                else if(val.indexOf('radial-gradient') !== -1) {
469
                    active_mode = 'radial-gradient';
470
                }
471
                else {
472
                    active_mode = 'solid';
473
                }
474
 
475
                if(active_mode == 'solid') {
476
                    active_solid = val;
477
                    active_gradient = options.fallback_colors[1];
478
                }
479
                else{
480
                    active_solid = options.fallback_colors[0];
481
                } */
482
            }
483
            active_trigger.style.background = val;
484
 
485
            if(!from_manual_input || (from_manual_input && options.open_on_focus)) {
486
                // elaborate solid color data (color and alpha)
487
                //this.load_solid_data(active_solid);
488
                // elaborate gradient data
489
                if(active_gradient) {
490
                    this.load_gradient_data(active_gradient);
491
                }
492
            }
493
        };
494
 
495
 
496
 
497
        /* elaborate solid color data (color and alpha) loading into active_solid and active_opacity */
498
        this.load_solid_data = function(raw_data) {
499
            active_opacity = 1;
500
 
501
            // rgba
502
            if(raw_data.indexOf('rgba') !== -1) {
503
                const data = this.RGBA_to_hexA(raw_data);
504
                active_solid = data[0];
505
                active_opacity = data[1];
506
            }
507
 
508
            // rgb
509
            else if(raw_data.indexOf('rgba') !== -1) {
510
                active_solid = this.RGB_to_hex(raw_data);
511
            }
512
 
513
            // hex
514
            else {
515
                active_solid = this.short_hex_fix(raw_data);
516
            }
517
        };
518
 
519
 
520
 
521
        /* elaborate gradient data loading into gradient_data */
522
        this.load_gradient_data = function(raw_data) {
523
            const $this = this;
524
            const is_radial = (raw_data.indexOf('radial-gradient') === -1) ? false : true;
525
 
526
            // solve issues with inner RGB|RGBA and turn everything into RGBA
527
            raw_data = raw_data
528
                .replace(/,\./g, ',0.').replace(/ \./g, ' 0.')
529
                .replace(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)/g, 'rgbaZ($1|$2|$3|$4)')
530
                .replace(/\|\)/g, '|1)');
531
 
532
            // names to deg
533
            raw_data = raw_data
534
                .replace('top right', '45deg').replace('right top', '45deg')
535
                .replace('bottom right', '135deg').replace('bottom right', '135deg')
536
                .replace('top left', '315deg').replace('left top', '315deg')
537
                .replace('bottom left', '225deg').replace('bottom left', '225deg')
538
                .replace('right', '90deg').replace('left', '270deg').replace('top', '0deg').replace('bottom', '180deg');
539
 
540
            // be sure deg or shape is defined
541
            if(is_radial && raw_data.indexOf('ellipse') === -1 && raw_data.indexOf('circle') === -1) {
542
                raw_data.replace('\\(', '(ellipse ');
543
            }
544
            if(!is_radial && raw_data.indexOf('deg') === -1) {
545
                raw_data.replace('\\(', '(180deg');
546
            }
547
 
548
            // process
549
            raw_data = raw_data.lccpReplaceArray(
550
                ['linear-gradient', 'radial-gradient', '', '\\(', 'to', '\\)'],
551
                ''
552
            );
553
 
554
            // split steps
555
            const raw_steps = raw_data.split(',');
556
            const fallback_multiplier = 100 / raw_steps.length;
557
 
558
            gradient_data.steps = [];
559
            raw_steps.some(function(raw_step, index) {
560
 
561
                // direction on first index
562
                if(!index) {
563
                    if(is_radial) {
564
                        gradient_data.radial_circle = (raw_step.indexOf('circle') === -1) ? false : true;
565
                    } else {
566
                        gradient_data.deg = parseInt(raw_step.replace('deg', ''), 10);
567
                    }
568
                }
569
 
570
                // {color : null, opacity: null, position : null}
571
                else {
572
                    raw_step = raw_step.trim().split(' ');
573
                    let position = '';
574
 
575
                    // position
576
                    if(raw_step.length < 2) {
577
                        if(index === 1) {
578
                            position = '0%';
579
                        }
580
                        else if(index == (raw_steps.length - 1)) {
581
                            position = '100%';
582
                        }
583
                        else {
584
                            position = (fallback_multiplier * index) +'%';
585
                        }
586
                    }
587
                    else {
588
                        position = raw_step[1];
589
                    }
590
 
591
                    // color
592
                    let raw_color   = raw_step[0],
593
                        opacity     = 1;
594
 
595
                    // normalize to hex
596
                    if(raw_color.indexOf('rgbaZ') !== -1) {
597
                        const col_arr = $this.RGBA_to_hexA(
598
                            raw_color.replace('rgbaZ', 'rgba').replace(/\|/g, ',')
599
                        );
600
 
601
                        raw_color = col_arr[0];
602
                        opacity = col_arr[1];
603
                    }
604
 
605
                    gradient_data.steps.push({
606
                        color : $this.short_hex_fix(raw_color),
607
                        opacity: opacity,
608
                        position : parseInt(position, 10)
609
                    });
610
                }
611
            });
612
        };
613
 
614
 
615
 
616
        /* handles RGBA string returning a two elements array: hex and alpha  */
617
        this.RGBA_to_hexA = function(raw_data) {
618
            raw_data = raw_data.lccpReplaceArray(['rgba', '\\(', '\\)'], '');
619
            const rgba_arr = raw_data.split(',')
620
 
621
            let alpha = (typeof(rgba_arr[3]) != 'undefined') ? rgba_arr[3] : '1';
622
            if(alpha.substring(0, 1) == '.') {
623
                alpha = 0 + alpha;
624
            }
625
            rgba_arr.splice(3, 1);
626
 
627
            return [
628
                this.RGB_to_hex('rgb('+ rgba_arr.join(',') +')'),
629
                parseFloat(alpha)
630
            ];
631
        };
632
 
633
 
634
 
635
        /* convert RGB to hex */
636
        this.RGB_to_hex = function(rgb) {
637
            rgb = rgb.lccpReplaceArray(['rgb', '\\(', '\\)'], '');
638
            const rgb_arr = rgb.split(',');
639
 
640
            if(rgb_arr.length < 3) {
641
                return '';
642
            }
643
 
644
            let r = parseInt(rgb_arr[0].trim(), 10).toString(16),
645
                g = parseInt(rgb_arr[1].trim(), 10).toString(16),
646
                b = parseInt(rgb_arr[2].trim(), 10).toString(16);
647
 
648
            if (r.length == 1) {r = "0" + r;}
649
            if (g.length == 1) {g = "0" + g;}
650
            if (b.length == 1) {b = "0" + b;}
651
 
652
            return this.shorten_hex(r + g + b);
653
        };
654
 
655
 
656
 
657
        /* if possible, shortenize hex string */
658
        this.shorten_hex = function(hex) {
659
            hex = hex.replace('#', '').split('');
660
 
661
            if(hex.length >= 6) {
662
                if(
663
                    hex[0] === hex[1] &&
664
                    hex[2] === hex[3] &&
665
                    hex[4] === hex[5]
666
                ) {
667
                    return '#'+ hex[0] + hex[2] + hex[4];
668
                }
669
            }
670
 
671
            return '#'+ hex.join('');
672
        };
673
 
674
 
675
 
676
        /* convert short hex to full format */
677
        this.short_hex_fix = function(hex) {
678
            if(hex.length == 4) {
679
                const a = hex.split('');
680
                hex = a[0] + a[1] + a[1] + a[2] + a[2] + a[3] + a[3];
681
            }
682
 
683
            return hex.toLowerCase();
684
        };
685
 
686
 
687
 
688
        /* convert hex to RGB */
689
        this.hex_to_RGB = function(h) {
690
            let r = 0, g = 0, b = 0;
691
 
692
            // 3 digits
693
            if (h.length == 4) {
694
                r = "0x" + h[1] + h[1];
695
                g = "0x" + h[2] + h[2];
696
                b = "0x" + h[3] + h[3];
697
 
698
                // 6 digits
699
            } else if (h.length == 7) {
700
                r = "0x" + h[1] + h[2];
701
                g = "0x" + h[3] + h[4];
702
                b = "0x" + h[5] + h[6];
703
            }
704
 
705
            return "rgb("+ +r + ", " + +g + ", " + +b + ")";
706
        };
707
 
708
 
709
 
710
        /* convert hex to RGB */
711
        this.hex_to_RGBA = function(h, opacity) {
712
            if(parseFloat(opacity) === 1) {
713
                return this.shorten_hex(h);
714
            }
715
 
716
            let rgb = this.hex_to_RGB(h);
717
            return rgb.replace('(', 'a(').replace(')', ', '+ opacity.toString().replace('0.', '.') +')');
718
        };
719
 
720
 
721
 
722
 
723
        /* append color container picker to the body */
724
        this.append_color_picker = function(on_manual_input_change = false) {
725
            const $this = this;
726
 
727
           /*  if(document.getElementById("lc-color-picker") && !on_manual_input_change) {
728
                document.getElementById("lc-color-picker").remove();
729
            } */
730
 
731
            const theme_class     = (options.dark_theme) ? 'lccp_dark_theme' : 'lccp_light_theme',
732
                  bg              = (active_mode == 'solid') ? active_solid : active_gradient,
733
                  shown_solid     = (active_mode == 'solid') ? active_solid : gradient_data.steps[0].color,
734
                  shown_opacity   = (active_mode == 'solid') ? active_opacity : (options.transparency) ? gradient_data.steps[0].opacity : null,
735
                  print_grad_code = (options.modes.indexOf('linear-gradient') !== -1 || options.modes.indexOf('radial-gradient') !== -1) ? true : false;
736
 
737
 
738
            // start code
739
            let picker = '',
740
                picker_el;
741
 
742
            if(on_manual_input_change && document.getElementById("lc-color-picker")) {
743
                picker_el = document.getElementById("lc-color-picker");
744
                picker_el.setAttribute('data-mode', active_mode);
745
                picker_el.setAttribute('data-trigger-id', cp_uniqid);
746
            }
747
            else {
748
                picker = '<div id="lc-color-picker" class="'+ theme_class +'" data-mode="'+ active_mode +'" data-trigger-id="'+ cp_uniqid +'">';
749
            }
750
 
751
 
752
            // modes select
753
            /* if(options.modes.length >= 1) {
754
                picker += `
755
                <div id="lccp_modes_wrap">
756
                    <span class="${(active_mode == 'solid') ? 'lccp_sel_mode' : ''}" ${(options.modes.indexOf('solid') === -1) ? 'style="display: none;"' : ''} data-mode="solid">${ options.labels[1] }</span>
757
                    <span class="${(active_mode == 'linear-gradient') ? 'lccp_sel_mode' : ''}" ${(options.modes.indexOf('linear-gradient') === -1) ? 'style="display: none;"' : ''} data-mode="linear-gradient">${ options.labels[2] }</span>
758
                    <span class="${(active_mode == 'radial-gradient') ? 'lccp_sel_mode' : ''}" ${(options.modes.indexOf('radial-gradient') === -1) ? 'style="display: none;"' : ''} data-mode="radial-gradient">${ options.labels[3] }</span>
759
                </div>`;
760
            } */
761
 
762
 
763
            // gradient wizard
764
            if(print_grad_code) {
765
                picker += `
766
                <div class="lccp_gradient_wizard" ${ (active_mode == 'solid') ? 'style="display: none;"' : '' }>
767
                    <div class="lccp_gradient lccp_gradient-bg"></div>
768
                    <div class="lccp_gradient" style="background: ${ active_gradient }" title="${ options.labels[4] }"></div>
769
 
770
                    <div class="lccp_gradient_ranges"></div>
771
 
772
                    <div class="pccp_deg_f_wrap" ${ (active_mode == 'radial-gradient') ? 'style="display: none;"' : '' }>
773
                        <img src="   data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAZJJREFUeNqsVetthDAMzp06QEagEv/LBk03oBswAhuQDVAn6AgZgTIBbAAb5DZIE+ScUs42OVRLVvBd7M+v2BeRRxVwEQSttei6LlNV/HB/ymDP8+LZYVxVlev73llrXUrLsqSipgBaz5YyvmcppfPRbRYDYJCNMSzId67xPSulXNu223c4KZDTAFgqMRDNKAXwGuokoAEazwMHBLW6gxTExQm6iqOGAoG66Ctc7BHl2fMHnBT1ECVK4zgKLgqbpIYjm1EX/eIFhSh/eb5lgLyC/jucf1I7z7NY1/Ue8t4LybwfDU1APeAabIZ6urIsg85DhyxM/tN7de5cuSK/rcz84uSnQKhU3Q7kNKUqDtNIJrMmEu4ORMvH6NDUtwhII85TBfXSYBtF54p/tBoGMI6mfCDm1bM0JU4+vD/FDMYc6pMUx11ksIuGAFrAAJYCtctEk5PT6WBPTOCMTKKPDljg4j+AHABEw0PSQBMxC8n3cASSbtLibL8rZvOpxHtztBYuGWAFPK63xOPPzFWw0a8AAwA+dEfwP/CgZgAAAABJRU5ErkJggg==" alt="angle" title="${ options.labels[5] }" />
774
 
775
                        <input type="range" name="deg" value="${ gradient_data.deg }" min="0" max="360" step="1" />
776
                        <input type="number" name="deg-num" value="${ gradient_data.deg }" min="0" max="360" step="1" />
777
                    </div>
778
                    <div class="pccp_circle_f_wrap" ${ (active_mode == 'radial-gradient') ? '' : 'style="display: none;"' }>
779
                        <img src="    data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAhQAAAIUB4uz/wQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAEZSURBVEiJzdQxSgRBFATQ52hionsKxcjQTERYUzMv4F28hImsiwcQExFEBfUCggtGXkBccRNR1qBnQHRme9p2wYJigu5f9Wd+zaceXexjgFeM8IADbGGmoS6KJVxiHOENVlLF1/HUQrziEBspnaeIV3zGchuD81+IV7wWmUk3Q7ziZpP4HHYib/eBU9yVYnWYnyQwmNDZI1YjDUTx0iD+Xop30BdSk5Kww7LWqOHSSdlAP0H4O3uEP7TucK80SOn8R4wLIWZ1eCufC03ftgUWCxxlCERR4Ay30zQYY1dI01QM4B7bwkCnYgAXWMPVXxo0LalNzArzaVoPWQZfkWVQxK/k4V8Y5MR32MbgOMOgVW1H2Iqp67qHzif097OAt54XXQAAAABJRU5ErkJggg==" alt="shape" title="${ options.labels[6] }" />
780
 
781
                        <span class="pcpp_ellipse_shape ${ (gradient_data.radial_circle) ? '' :  'pcpp_circle_btn_active' }" data-val="ellipse">Ellipse</span>
782
                        <span class="pcpp_circle_shape ${ (gradient_data.radial_circle) ? 'pcpp_circle_btn_active' : '' }" data-val="circle">Circle</span>
783
                    </div>
784
                    <hr/>
785
                </div>`;
786
            }
787
 
788
 
789
            // HTML5 colorpicker
790
            picker += `
791
            <div class="pccp_color_f_wrap" ${ (!print_grad_code) ? 'style="margin-top: 0;"' : '' }>
792
                <img src="  data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAA2wAAANsB8FDmnAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAFzSURBVEiJrdU/SxxRFIbx3wgWCjbRSkEICCHBzhCwUCurQDr3I6QLaJEqTcDWJoVNPoAWqW2sXAJW22hhkX+F1VYKBlwQ15ti7+Cy2Z29M+OFt5i5nOdhztw5I4QgNXiFH/iYXFMCPoFThJhPTy340AcPeEDjSQRYxN8BQcAt3tQSYBInQ+B52pitI/haAM/zpZIAOwnwgDsslRLgLbqJgoBvyQIs46YEPM/qWAGm8asCPOB7imCvIjzPy5ECrOC+pmC3SJByJMflvEhwUQO8j0t0kP0nwDO9+VIFfh8Px0m8nh8mmKrR/zO9aXsTGVM5d0JcIYRObFGV1cQLzOAisojW/tWqAG/jM94PZQy85IZyrenqjZR1j2OlMfIURclRIvwP1jCH3/HeUcqXvIDrEdCfOMC2Xr/fxRaFWLOQOuw2+wrzHMe959iKonyvjc2y43oWhwOSqyFPdajgj5ZF2MiVZdkGNvA6Jj8pLTRDCM2i+n/jlZhJOn4yuAAAAABJRU5ErkJggg==" alt="color" title="${ options.labels[7] }" />
793
 
794
                <div>
795
                    <input type="color" name="color" value="${ shown_solid }" style="opacity: ${ active_opacity };" />
796
                </div>
797
                <input type="text" name="hex" value="${ shown_solid.toLowerCase() }" />
798
            </div>`;
799
 
800
            // opacity cursor
801
            if(options.transparency) {
802
                picker += `
803
                <div class="pccp_opacity_f_wrap">
804
                    <img src=" data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAApgAAAKYB3X3/OAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAF9SURBVEiJtda/ihRBEMfxT42D4Jmdq5GHIBqJIHgYGCuIIhcYaGRgYGDsU4i+gqFgJogXivgGwkUmKpqcnhgJJ/4pg5mV3XVmd3q5K+iku+v37e7qrurITH0WEUdwC5dwCiexi21sZub9XufW6h7ho3iEmzg4M/wOm3i/SBxk5lTDDXxB9rRnHT7H8QYX/hubmXgHf+aI9wEO4CW+Yb0T0B7HIvFOQOu/glct5PwUAMfwdYB4L6DVOYzX+DTuGwf5IVYHBW2OZeb3iLiC9cn+EX4OXP3cHXS1qj37zuu6F1bjaqFP/8ucsIi4jtM1ThQCYuC8azhXYa0QUGTVfoqPAR/3E1DjA84U+AwKMl7gba3JjCU3aVCQM/M5zRE9xa8CQJFVmbmDJ3slGBGHIuLiv442SY3MrwFLJ7uqhezgriZdL7vyFU1gz2JjagcTK7iN36U7MKTgzJTMz4WANWxZVDInHEZ4jB8dgC08wL0h6ToWfFtWNd+Wy5b8tvwFZS60TLZpD/8AAAAASUVORK5CYII=" alt="opacity" title="${ options.labels[8] }" />
805
 
806
                    <input type="range" name="opacity" value="${ shown_opacity }" min="0" max="1" step="0.01" />
807
                    <input type="number" name="opacity-num" value="${ shown_opacity }" min="0" max="1" step="0.05" />
808
                </div>`;
809
            }
810
 
811
 
812
            // append or re-fill
813
            (on_manual_input_change && document.getElementById("lc-color-picker")) ? picker_el.innerHTML = picker : document.body.insertAdjacentHTML('beforeend', picker +'</div>');
814
 
815
 
816
            // modes change
817
            if(options.modes.length >= 1) {
818
                for (const mode of document.querySelectorAll('#lccp_modes_wrap span')) {
819
                    mode.addEventListener("click", (e) => { $this.mode_change( e.target, e.target.getAttribute('data-mode')) });
820
                }
821
            }
822
 
823
            // print steps and add gradient step action
824
            if(print_grad_code) {
825
                gradient_data.steps.some(function(step, index) {
826
                    $this.add_draggable_element(index, step.position, step.color);
827
                });
828
 
829
                document.querySelector('.lccp_gradient:not(.lccp_gradient-bg)').addEventListener("click", (e) => {this.add_gradient_step(e) });
830
            }
831
 
832
            // angle actions
833
            if(options.modes.indexOf('linear-gradient') !== -1) {
834
                document.querySelector('.pccp_deg_f_wrap input[type=range]').addEventListener("input", (e) => {this.track_deg_range_change(e)});
835
                document.querySelector('.pccp_deg_f_wrap input[name=deg-num]').addEventListener("change", (e) => {this.track_deg_num_change(e)});
836
                document.querySelector('.pccp_deg_f_wrap input[name=deg-num]').addEventListener("keyup", (e) => {
837
                    this.debounce('deg_f_change', 500, 'track_deg_num_change', e);
838
                });
839
            }
840
 
841
            // circle actions
842
            if(options.modes.indexOf('radial-gradient') !== -1) {
843
                for (const mode of document.querySelectorAll('.pccp_circle_f_wrap span')) {
844
                    mode.addEventListener("click", (e) => { $this.set_ellipse_circle( e.target, e.target.getAttribute('data-val')) });
845
                }
846
            }
847
 
848
            // color actions
849
            document.querySelector('.pccp_color_f_wrap input[type="color"]').addEventListener("input", (e) => {this.track_color_change(e)});
850
            document.querySelector('.pccp_color_f_wrap input[type="color"]').addEventListener("change", (e) => {this.track_color_change(e)});
851
            document.querySelector('.pccp_color_f_wrap input[name=hex]').addEventListener("keyup", (e) => {
852
                this.debounce('hex_f_change', 600, 'track_color_hex_change', e);
853
            });
854
 
855
            // transparency actions
856
            if(options.transparency) {
857
                document.querySelector('.pccp_opacity_f_wrap input[type=range]').addEventListener("input", (e) => {this.track_opacity_range_change(e)});
858
                document.querySelector('.pccp_opacity_f_wrap input[name=opacity-num]').addEventListener("change", (e) => {this.track_opacity_num_change(e)});
859
                document.querySelector('.pccp_opacity_f_wrap input[name=opacity-num]').addEventListener("keyup", (e) => {
860
                    this.debounce('opacity_f_change', 500, 'track_opacity_num_change', e);
861
                });
862
            }
863
        };
864
 
865
 
866
 
867
        /*** add draggable element ***/
868
        this.add_draggable_element = function(rel_step_num, position, color) {
869
            const   $this       = this,
870
                    container   = document.querySelector('.lccp_gradient_ranges'),
871
                    sel_class   = (!rel_step_num) ? 'lccp_sel_step' : '',
872
                    del_btn_vis = (gradient_data.steps.length > 2) ? '' : 'style="display: none;"'
873
 
874
            container.innerHTML +=
875
            '<span class="lccp_gradient_range '+ sel_class +'" data-step-num="'+ rel_step_num +'" style="background: '+ color +'; left: '+ position +'%;">'+
876
                '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAJFJREFUeNpiYMAOFIC4H4jPA/F/NHweKqfAQAQQgCr+TyTuJ2TYeRIMQ3axADYD8RlWAMVEG9pAwBUwQJT3FYjwFjEGgrACE5BIYKAeyGcgMiKIdeF9RjQNuAAjkoEEAXpskgpQYp+JgcqA6l4GufACFR34AGTgRioauIHqCZsmWY8mhQNNii+qF7BUqQIAAgwANgu7Y7cw5VAAAAAASUVORK5CYII=" '+ del_btn_vis +' />'+
877
            '</span>';
878
 
879
            let active = false;
880
 
881
            //////
882
            const dragStart = function(range_id, el, e) {
883
                active = range_id;
884
            };
885
 
886
            const dragEnd = function() {
887
                active = false;
888
                $this.apply_changes();
889
            };
890
 
891
            const drag = function(range_id, range, e) {
892
                if (active !== false && range_id == active) {
893
                    e.preventDefault();
894
                    const rect = container.getBoundingClientRect();
895
 
896
                    let new_pos = (e.type === "touchmove") ? (e.touches[0].clientX - rect.left) : (e.clientX - rect.left);
897
                    new_pos = Math.round((100 * new_pos) / container.offsetWidth);
898
 
899
                    if(new_pos < 0) {new_pos = 0;}
900
                    else if(new_pos > 100) {new_pos = 100;}
901
 
902
                    // limit positions basing on previous and next step
903
                    const min_pos = (!range_id) ? 0 : gradient_data.steps[ range_id-1 ].position;
904
                    const max_pos = (range_id == (gradient_data.steps.length - 1)) ? 100 : gradient_data.steps[ range_id+1 ].position;
905
 
906
                    if(new_pos < min_pos) {new_pos = min_pos + 1;}
907
                    else if(new_pos > max_pos) {new_pos = max_pos - 1;}
908
 
909
                    gradient_data.steps[ range_id ].position = new_pos;
910
                    range.style.left = new_pos +'%';
911
 
912
                    $this.apply_gradient_changes();
913
                }
914
            };+
915
            /////
916
 
917
            document.querySelectorAll('.lccp_gradient_range').forEach(range => {
918
                const step_num = parseInt(range.getAttribute('data-step-num'), 10);
919
 
920
                range.removeEventListener("touchstart", null);
921
                range.removeEventListener("touchend", null);
922
                range.removeEventListener("touchmove", null);
923
                range.removeEventListener("click", null);
924
 
925
                range.removeEventListener("mousedown", null);
926
                range.removeEventListener("mouseup", null);
927
 
928
                range.addEventListener("touchstart", (e) => {dragStart(step_num, e.target, e)});
929
                range.addEventListener("mousedown", (e) => {dragStart(step_num, e.target, e)});
930
 
931
                range.addEventListener("click", (e) => {$this.select_gradient_color(step_num)});
932
 
933
                container.addEventListener("touchmove", (e) => {drag(step_num, range, e)});
934
                container.addEventListener("mousemove", (e) => {drag(step_num, range, e)});
935
 
936
                range.addEventListener("mouseup", (e) => {dragEnd()});
937
                range.addEventListener("touchend", (e) => {dragEnd()});
938
                document.addEventListener("mouseup", (e) => {dragEnd()});
939
            });
940
 
941
 
942
            // remove step handler
943
            document.querySelectorAll('.lccp_gradient_range img').forEach((btn) => {
944
 
945
                btn.addEventListener("click", (e) => {
946
                    if(document.querySelectorAll('.lccp_gradient_range').length < 3) {
947
                        return false;
948
                    }
949
 
950
                    // wait a bit to not interfere with global handler for picker closing
951
                    setTimeout(() => {
952
                        const parent = e.target.parentNode,
953
                              step_num = parseInt(parent.getAttribute('data-step-num'), 10),
954
                              to_select = (!step_num) ? 0 : step_num - 1;
955
 
956
                        gradient_data.steps.splice(step_num, 1);
957
 
958
                        // clean and restart
959
                        document.querySelectorAll('.lccp_gradient_range').forEach(r => r.remove());
960
 
961
                        gradient_data.steps.some(function(step, index) {
962
                            $this.add_draggable_element(index, step.position, step.color);
963
                        });
964
 
965
                        // select newly added element
966
                        document.querySelector('.lccp_gradient_range[data-step-num="'+ to_select +'"]').click();
967
 
968
                        this.apply_gradient_changes(true);
969
                    }, 20);
970
                });
971
            });
972
        };
973
 
974
 
975
 
976
        /* select gradient color  */
977
        this.select_gradient_color = function(step_num) {
978
            sel_grad_step = step_num;
979
 
980
            document.querySelectorAll('.lccp_gradient_range').forEach(m => m.classList.remove('lccp_sel_step'));
981
            document.querySelector('.lccp_gradient_range[data-step-num="'+ step_num +'"]').classList.add('lccp_sel_step');
982
 
983
            active_solid = gradient_data.steps[ step_num ].color;
984
            active_opacity = gradient_data.steps[ step_num ].opacity;
985
 
986
            document.querySelector('#lc-color-picker input[type="color"]').value = active_solid;
987
            document.querySelector('.pccp_color_f_wrap input[name=hex]').value = active_solid;
988
 
989
            if(options.transparency) {
990
                document.querySelector('.pccp_opacity_f_wrap input[type=range]').value = active_opacity;
991
                document.querySelector('.pccp_opacity_f_wrap input[name=opacity-num]').value = active_opacity;
992
            }
993
        };
994
 
995
 
996
 
997
        /* apply changes to gradient, after a color/opacity/degree update */
998
        this.apply_gradient_changes = function(also_apply_changes) {
999
            const $this = this;
1000
 
1001
            let new_gradient = active_mode+'(';
1002
            new_gradient += (active_mode == 'linear-gradient') ? gradient_data.deg+'deg' : (gradient_data.radial_circle) ? 'circle' : 'ellipse';
1003
            new_gradient += ', ';
1004
 
1005
            let colors_part = []
1006
            gradient_data.steps.some(function(step, index) {
1007
 
1008
                let to_add = (options.transparency) ? $this.hex_to_RGBA(step.color, step.opacity) : $this.shorten_hex(step.color);
1009
 
1010
                if(
1011
                    gradient_data.steps.length > 2 ||
1012
                    (
1013
                        gradient_data.steps.length <= 2 &&
1014
                        (
1015
                            (!index && parseInt(step.position, 10)) ||
1016
                            (index && index < (gradient_data.steps.length - 1)) ||
1017
                            (index == (gradient_data.steps.length - 1) && parseInt(step.position, 10) != 100)
1018
                        )
1019
                    )
1020
                ) {
1021
                        to_add += ' '+ step.position +'%';
1022
                    }
1023
 
1024
                colors_part.push( to_add );
1025
            });
1026
 
1027
            active_gradient = new_gradient + colors_part.join(', ') + ')';
1028
 
1029
            if(document.querySelector('.lccp_gradient:not(.lccp_gradient-bg)')) {
1030
                document.querySelector('.lccp_gradient:not(.lccp_gradient-bg)').style.background = active_gradient;
1031
            }
1032
 
1033
            if(also_apply_changes) {
1034
                this.apply_changes();
1035
            }
1036
        };
1037
 
1038
 
1039
 
1040
        /* apply changes to target field */
1041
        this.apply_changes = function() {
1042
            if(!active_trigger) {
1043
                return false;
1044
            }
1045
            let val = '';
1046
 
1047
            // apply everything to picker global vars
1048
            if(active_mode == 'solid') {
1049
                val = this.shorten_hex(active_solid);
1050
 
1051
                if(options.transparency && document.querySelector('.pccp_opacity_f_wrap input[type=range]')) {
1052
                    active_opacity = document.querySelector('.pccp_opacity_f_wrap input[type=range]').value;
1053
                    val = this.hex_to_RGBA(val, active_opacity);
1054
                }
1055
            }
1056
            else {
1057
                val = active_gradient;
1058
            }
1059
 
1060
            // apply
1061
            active_trigger.style.background = val;
1062
 
1063
            const field = active_trigger.parentNode.querySelector(right_input_selector),
1064
                  old_val = field.value;
1065
 
1066
            if(old_val != val) {
1067
                field.value = val;
1068
                last_tracked_col = val;
1069
 
1070
                if(typeof(options.on_change) == 'function') {
1071
 
1072
                    if(typeof(debounced_vars['on_change_cb']) != undefined && debounced_vars['on_change_cb']) {
1073
                        clearTimeout(debounced_vars['on_change_cb']);
1074
                    }
1075
                    debounced_vars['on_change_cb'] = setTimeout(() => {
1076
                        options.on_change.call(this, val, field);
1077
                    }, 300);
1078
                }
1079
            }
1080
        };
1081
 
1082
 
1083
 
1084
 
1085
 
1086
 
1087
        // HANDLERS
1088
 
1089
        // fields toggle basing on modes change
1090
        this.mode_change = function(el, new_mode) {
1091
            if(active_mode == new_mode) {
1092
                return false;
1093
            }
1094
            let color, opacity;
1095
 
1096
            // from gradient to solid
1097
            if(new_mode == 'solid') {
1098
                color = active_solid;
1099
                if(options.transparency) {
1100
                    opacity = active_opacity;
1101
                }
1102
            }
1103
            else {
1104
                color = gradient_data.steps[0].color;
1105
                if(options.transparency) {
1106
                    opacity = gradient_data.steps[0].opacity;
1107
                }
1108
            }
1109
 
1110
            document.querySelector('#lc-color-picker input[type="color"]').value = color;
1111
            document.querySelector('.pccp_color_f_wrap input[name=hex]').value = color;
1112
 
1113
            if(options.transparency) {
1114
                document.querySelector('.pccp_opacity_f_wrap input[type=range]').value = opacity;
1115
                document.querySelector('.pccp_opacity_f_wrap input[name=opacity-num]').value = opacity;
1116
            }
1117
 
1118
            // toggle grad fields
1119
            if(options.modes.length >= 1) {
1120
                document.querySelector('.pccp_deg_f_wrap').style.display = (new_mode == 'linear-gradient') ? 'flex' : 'none';
1121
                document.querySelector('.pccp_circle_f_wrap').style.display = (new_mode == 'radial-gradient') ? 'block' : 'none';
1122
            }
1123
 
1124
            // toogle gradient wizard
1125
            if(options.modes.indexOf('linear-gradient') !== -1 || options.modes.indexOf('radial-gradient') !== -1) {
1126
                document.querySelector('.lccp_gradient_wizard').style.display = (new_mode != 'solid') ? 'block' : 'none';
1127
            }
1128
 
1129
            document.querySelectorAll('#lccp_modes_wrap span').forEach(m => m.classList.remove('lccp_sel_mode'));
1130
            el.classList.add('lccp_sel_mode');
1131
 
1132
            active_mode = new_mode;
1133
            (new_mode == 'solid') ? this.apply_changes() : this.apply_gradient_changes(true);
1134
        };
1135
 
1136
 
1137
        // add gradient step
1138
        this.add_gradient_step = function(e) {
1139
            const   $this = this,
1140
                    pos = Math.round((100 * e.layerX) / e.target.offsetWidth);
1141
 
1142
            // inject in actual steps
1143
            let index = 0;
1144
            for(let step of gradient_data.steps) {
1145
 
1146
                if(step.position > pos) {
1147
                    const step_data = {
1148
                        color       : (index - 1 < 0) ? step.color : gradient_data.steps[(index - 1)].color,
1149
                        opacity     : 1,
1150
                        position    : pos
1151
                    }
1152
 
1153
                    gradient_data.steps.splice(index, 0, step_data);
1154
                    break;
1155
                }
1156
 
1157
                index++;
1158
            }
1159
            document.querySelectorAll('.lccp_gradient_range').forEach(r => r.remove());
1160
 
1161
            gradient_data.steps.some(function(step, index) {
1162
                $this.add_draggable_element(index, step.position, step.color);
1163
            });
1164
 
1165
            // select newly added element
1166
            document.querySelector('.lccp_gradient_range[data-step-num="'+ index +'"]').click();
1167
 
1168
            this.apply_gradient_changes(true);
1169
        };
1170
 
1171
 
1172
        // apply ellipse or circle
1173
        this.set_ellipse_circle = function(el, new_opt) {
1174
            if(gradient_data.radial_circle && new_opt == 'circle' || !gradient_data.radial_circle && new_opt != 'circle') {
1175
                return false;
1176
            }
1177
            gradient_data.radial_circle = !gradient_data.radial_circle;
1178
 
1179
            document.querySelectorAll('.pccp_circle_f_wrap span').forEach(m => m.classList.remove('pcpp_circle_btn_active'));
1180
            el.classList.add('pcpp_circle_btn_active');
1181
 
1182
            this.apply_gradient_changes(true);
1183
        };
1184
 
1185
 
1186
        // track opacity range fields change
1187
        this.track_deg_range_change = function(e) {
1188
            document.querySelector('.pccp_deg_f_wrap input[name=deg-num]').value = e.target.value;
1189
 
1190
            gradient_data.deg = e.target.value;
1191
            this.apply_gradient_changes(true);
1192
        };
1193
        this.track_deg_num_change = function(e) {
1194
            let val = parseFloat(e.target.value);
1195
            if(isNaN(val) || val < 0 || val > 360) {
1196
                val = 90;
1197
            }
1198
 
1199
            e.target.value = val;
1200
            if(document.querySelector('.pccp_deg_f_wrap input[type=range]')) {
1201
                document.querySelector('.pccp_deg_f_wrap input[type=range]').value = val;
1202
            }
1203
 
1204
            gradient_data.deg = val;
1205
            this.apply_gradient_changes(true);
1206
        };
1207
 
1208
 
1209
        // track opacity range fields change
1210
        this.track_color_change = function(e) {
1211
            const val = e.target.value.toLowerCase();
1212
            document.querySelector('.pccp_color_f_wrap input[name=hex]').value = val;
1213
 
1214
            this.apply_color_change(val);
1215
        };
1216
        this.track_color_hex_change = function(e) {
1217
            let val = this.short_hex_fix(e.target.value);
1218
 
1219
            if(val.match(/^#[a-f0-9]{6}$/i) === null) {
1220
                val = active_solid.toLowerCase();
1221
            }
1222
 
1223
            e.target.value = val;
1224
            document.querySelector('#lc-color-picker input[type="color"]').value = val;
1225
 
1226
            this.apply_color_change(val);
1227
        };
1228
        this.apply_color_change = function(val) {
1229
            if(active_mode == 'solid') {
1230
                active_solid = val;
1231
                this.apply_changes();
1232
            }
1233
            else {
1234
                gradient_data.steps[ sel_grad_step ].color = val;
1235
 
1236
                document.querySelector('.lccp_sel_step').style.background = val;
1237
                this.apply_gradient_changes(true);
1238
            }
1239
        };
1240
 
1241
 
1242
        // track opacity range fields change
1243
        this.track_opacity_range_change = function(e) {
1244
            document.querySelector('.pccp_opacity_f_wrap input[name=opacity-num]').value = e.target.value;
1245
            this.alter_hex_opacity(e.target.value);
1246
        };
1247
        this.track_opacity_num_change = function(e) {
1248
            let val = parseFloat(e.target.value);
1249
            if(isNaN(val) || val < 0 || val > 1) {
1250
                val = 0.5;
1251
            }
1252
 
1253
            e.target.value = val;
1254
 
1255
            if(document.querySelector('.pccp_opacity_f_wrap input[type=range]')) {
1256
                document.querySelector('.pccp_opacity_f_wrap input[type=range]').value = val;
1257
                this.alter_hex_opacity(val);
1258
            }
1259
        };
1260
        this.alter_hex_opacity = function(opacity) {
1261
            document.querySelector('#lc-color-picker input[type="color"]').style.opacity = opacity;
1262
 
1263
            if(active_mode == 'solid') {
1264
                active_opacity = opacity;
1265
                this.apply_changes();
1266
            }
1267
            else {
1268
                gradient_data.steps[ sel_grad_step ].opacity = opacity;
1269
                this.apply_gradient_changes(true);
1270
            }
1271
        };
1272
 
1273
 
1274
 
1275
 
1276
 
1277
        /*
1278
         * UTILITY FUNCTION - debounce action to run once after X time
1279
         *
1280
         * @param (string) action_name
1281
         * @param (int) timing - milliseconds to debounce
1282
         * @param (string) - class method name to call after debouncing
1283
         * @param (mixed) - extra parameters to pass to callback function
1284
         */
1285
        this.debounce = function(action_name, timing, cb_function, cb_params) {
1286
            if( typeof(debounced_vars[ action_name ]) != 'undefined' && debounced_vars[ action_name ]) {
1287
                clearTimeout(debounced_vars[ action_name ]);
1288
            }
1289
            const $this = this;
1290
 
1291
            debounced_vars[ action_name ] = setTimeout(() => {
1292
                $this[cb_function].call($this, cb_params);
1293
            }, timing);
1294
        };
1295
 
1296
 
1297
 
1298
 
1299
 
1300
        /* CSS - creates inline CSS into the page */
1301
        this.generate_style = function() {
1302
            const transp_bg_img = "url('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAQDAwMDAwQDAwQGBAMEBgcFBAQFBwgGBgcGBggKCAkJCQkICgoMDAwMDAoMDA0NDAwRERERERQUFBQUFBQUFBT/2wBDAQQFBQgHCA8KCg8UDg4OFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAAoACgDAREAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAUGCP/EACIQAQAABQMFAQAAAAAAAAAAAAAFFUNjgqLB4RESEzVRkf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwDv4AE6K0stgTgAaIAE6K0stgTgAUZra1cATW1q4A9na8WXXu/PgEqu6eQJVd08gnAAowqrjuCiADOgAowqrjuCiAD/2Q==')";
1303
 
1304
            document.head.insertAdjacentHTML('beforeend',
1305
`<style>
1306
.lccp-el-wrap {
1307
    position: relative;
1308
    display: inline-block;
1309
}
1310
.lccp-el-wrap > input {
1311
    margin: 0;
1312
    min-width: 100%;
1313
    max-width: 100%;
1314
    width: auto;
1315
}
1316
.lccp-preview,
1317
.lccp-preview-bg {
1318
    display: inline-block;
1319
    position: absolute;
1320
    cursor: pointer;
1321
    z-index: 15;
1322
}
1323
.lccp-preview-bg {
1324
    z-index: 10;
1325
}
1326
.lccp-preview-right .lccp-preview {
1327
    border-left: 1px solid #ccc;
1328
}
1329
.lccp-preview-left .lccp-preview {
1330
    border-right: 1px solid #ccc;
1331
}
1332
.lccp-direct-cp-f {
1333
    padding: 0 !important;
1334
    margin: 0 !important;
1335
    width: 0 !important;
1336
    height: 0 !important;
1337
    position: absolute;
1338
    bottom: 0;
1339
    visibility: hidden;
1340
}
1341
.lccp-preview-right .lccp-direct-cp-f {
1342
    right: 0;
1343
}
1344
.lccp-preview-left .lccp-direct-cp-f{
1345
    left: 0;
1346
}
1347
#lc-color-picker,
1348
#lc-color-picker * {
1349
    box-sizing: border-box;
1350
    font-family: sans-serif;
1351
}
1352
#lc-color-picker {
1353
    visibility: hidden;
1354
    z-index: -100;
1355
    opacity: 0;
1356
    position: absolute;
1357
    top: -9999px;
1358
    z-index: 9999999999;
1359
    width: 280px;
1360
    background: #fff;
1361
    box-shadow: 0px 2px 13px -2px rgba(0, 0, 0, .18);
1362
    border-radius: 4px;
1363
    overflow: hidden;
1364
    padding: 10px;
1365
    border: 1px solid #ccc;
1366
    transition: opacity .15s ease;
1367
}
1368
#lc-color-picker.lccp-shown {
1369
    visibility: visible;
1370
    opacity: 1;
1371
 
1372
}
1373
 
1374
#lccp_modes_wrap {
1375
	display: flex;
1376
	flex-direction: row;
1377
	justify-content: space-between;
1378
    margin-bottom: 10px;
1379
}
1380
#lccp_modes_wrap span,
1381
.pccp_circle_f_wrap span {
1382
	display: inline-block;
1383
	border: 1px solid #e8e8e8;
1384
	background: #e8e8e8;
1385
    opacity: .78;
1386
	padding: 4px 7px;
1387
	font-size: 11.5px;
1388
	border-radius: 3px;
1389
	line-height: normal;
1390
    cursor: pointer;
1391
    user-select: none;
1392
    transition: all .2s ease;
1393
}
1394
#lccp_modes_wrap span.lccp_sel_mode,
1395
.pccp_circle_f_wrap span.pcpp_circle_btn_active {
1396
	border: 1px solid #bbb;
1397
	background: #fff;
1398
    opacity: 1;
1399
	cursor: default;
1400
}
1401
.lccp_gradient_wizard,
1402
.lccp_gradient_ranges {
1403
    position: relative;
1404
}
1405
.lccp_gradient {
1406
    height: 35px;
1407
    border: 1px solid #aaa;
1408
    cursor: crosshair;
1409
    position: relative;
1410
    z-index: 10;
1411
    user-select: none;
1412
}
1413
.lccp_gradient-bg {
1414
	position: absolute;
1415
	top: 0;
1416
	z-index: 0;
1417
	width: 100%;
1418
	margin: 0;
1419
}
1420
.lccp_gradient_ranges {
1421
    margin: 2px 7px 25px 8px;
1422
    height: 20px;
1423
}
1424
.lccp_gradient_range {
1425
	display: inline-block;
1426
	width: 13px;
1427
	height: 13px;
1428
	border: 1px solid #ccc;
1429
	border-radius: 0 50% 50% 50%;
1430
	transform: rotate(45deg) translate3d(-5px, 5px, 0);
1431
	cursor: col-resize;
1432
	position: absolute;
1433
    top: 3px;
1434
}
1435
.lccp_gradient_range img {
1436
	width: 13px;
1437
	position: relative;
1438
	top: 11px;
1439
	left: 11px;
1440
	opacity: .3;
1441
	cursor: pointer;
1442
    transition: all .2s ease;
1443
}
1444
.lccp_gradient_range img:hover {
1445
    opacity: .5;
1446
}
1447
.lccp_sel_step {
1448
    border: 1px solid #333;
1449
    box-shadow: 0 0 2px 1px teal;
1450
}
1451
.pccp_deg_f_wrap,
1452
.pccp_circle_f_wrap {
1453
    margin-bottom: 10px;
1454
}
1455
.pccp_circle_f_wrap * {
1456
    float: left;
1457
}
1458
.pccp_circle_f_wrap:after {
1459
    content: "";
1460
    clear: both;
1461
    display: block;
1462
}
1463
.pccp_circle_f_wrap img {
1464
    position: relative;
1465
    top: 4px;
1466
}
1467
.pccp_circle_f_wrap span {
1468
    margin-left: 13px;
1469
}
1470
.pccp_circle_f_wrap span:not(.pcpp_circle_btn_active) {
1471
    cursr: pointer;
1472
}
1473
.pccp_deg_f_wrap,
1474
.pccp_color_f_wrap,
1475
.pccp_opacity_f_wrap {
1476
    display: flex;
1477
    flex-direction: row;
1478
    justify-content: space-between;
1479
    align-items: center;
1480
    clear: both;
1481
}
1482
.pccp_deg_f_wrap input,
1483
.pccp_color_f_wrap input,
1484
.pccp_opacity_f_wrap input {
1485
    border: 1px solid #aaa;
1486
    border-radius: 3px;
1487
    padding: 0;
1488
}
1489
.lccp-preview-bg,
1490
.pccp_color_f_wrap div,
1491
.lccp_gradient-bg {
1492
    background: ${ transp_bg_img } repeat;
1493
    background-size: 15px;
1494
}
1495
#lc-color-picker hr {
1496
	margin: 14px 0 0;
1497
	height: 0;
1498
	border-width: 1px 0;
1499
	border-style: dashed;
1500
	border-color: #e3e3e3;
1501
}
1502
.pccp_color_f_wrap div {
1503
    width: calc(100% - 25px - 85px);
1504
    height: 25px;
1505
    border: 1px solid #aaa;
1506
    border-radius: 2px;
1507
    overflow: hidden;
1508
}
1509
.pccp_deg_f_wrap img,
1510
.pccp_circle_f_wrap img,
1511
.pccp_color_f_wrap img,
1512
.pccp_opacity_f_wrap img {
1513
	max-width: 15px;
1514
	opacity: .6;
1515
    cursor: help;
1516
    user-select: none;
1517
}
1518
.pccp_color_f_wrap input[type=color] {
1519
    -webkit-appearance: none;
1520
    padding: 0;
1521
    width: 110%;
1522
    height: 110%;
1523
    transform: translate(-5%, -5%);
1524
    cursor: pointer;
1525
    border: none;
1526
}
1527
.pccp_color_f_wrap input:focus {
1528
    outline: none;
1529
}
1530
.pccp_color_f_wrap input::-webkit-color-swatch-wrapper {
1531
    padding: 0;
1532
}
1533
.pccp_color_f_wrap input::-webkit-color-swatch {
1534
    border: none;
1535
}
1536
.pccp_color_f_wrap input[name=hex] {
1537
    width: 70px;
1538
    height: 25px;
1539
    text-align: center;
1540
}
1541
.pccp_color_f_wrap {
1542
    margin-top: 17px;
1543
}
1544
.pccp_opacity_f_wrap {
1545
    margin-top: 10px;
1546
}
1547
.pccp_deg_f_wrap input[type=range],
1548
.pccp_opacity_f_wrap input[type=range] {
1549
    width: calc(100% - 25px - 70px);
1550
    height: 25px;
1551
}
1552
.pccp_deg_f_wrap input[type="number"],
1553
.pccp_opacity_f_wrap input[type="number"] {
1554
	width: 53px;
1555
	height: 25px;
1556
	text-align: center;
1557
}
1558
.pccp_deg_f_wrap input[type=range],
1559
.pccp_opacity_f_wrap input[type=range] {
1560
    -webkit-appearance: none;
1561
    height: 5px;
1562
    background: #d5d5d5;
1563
    outline: none;
1564
    border: none;
1565
}
1566
.pccp_deg_f_wrap input::-webkit-slider-thumb,
1567
.pccp_opacity_f_wrap input::-webkit-slider-thumb {
1568
    -webkit-appearance: none;
1569
    appearance: none;
1570
    width: 17px;
1571
    height: 17px;
1572
    background: #888;
1573
    cursor: pointer;
1574
    border-radius: 50%;
1575
    border: 1px solid #aaa;
1576
    box-shadow: 0 0 0 5px #fff inset, 0 0 2px rgba(0,0,0,.15);
1577
}
1578
.pccp_deg_f_wrap input::-moz-range-thumb,
1579
.pccp_opacity_f_wrap input::-moz-range-thumb {
1580
    width: 15px;
1581
    height: 15px;
1582
    background: #888;
1583
    cursor: pointer;
1584
    border-radius: 50%;
1585
    border: 1px solid #aaa;
1586
    box-shadow: 0 0 0 5px #fff inset, 0 0 2px rgba(0,0,0,.15);
1587
}
1588
 
1589
#lc-color-picker.lccp_dark_theme {
1590
    background: #333;
1591
    border-color: #505050;
1592
}
1593
.lccp_dark_theme img {
1594
	filter: invert(100%);
1595
    opacity: .85;
1596
}
1597
.lccp_dark_theme .lccp_gradient_range img {
1598
	opacity: .6;
1599
}
1600
.lccp_dark_theme .lccp_gradient_range img:hover {
1601
    opacity: .8;
1602
}
1603
.lccp_dark_theme .lccp_gradient {
1604
    border-color: #626262;
1605
}
1606
.lccp_dark_theme .lccp_sel_step {
1607
	box-shadow: 0 0 2px 1px orange;
1608
}
1609
#lc-color-picker.lccp_dark_theme hr {
1610
	border-color: #727272;
1611
}
1612
.lccp_dark_theme .pccp_deg_f_wrap input,
1613
.lccp_dark_theme .pccp_color_f_wrap input,
1614
.lccp_dark_theme .pccp_opacity_f_wrap input {
1615
	border-color: #777;
1616
	background: #505050;
1617
	color: #f3f3f3;
1618
}
1619
.lccp_dark_theme input[type=range] {
1620
    background: #808080;
1621
}
1622
</style>`);
1623
        };
1624
 
1625
 
1626
        // init when called
1627
        this.init();
1628
    };
1629
 
1630
 
1631
 
1632
 
1633
 
1634
 
1635
    // UTILITIES
1636
 
1637
    // sanitize "selector" parameter allowing both strings and DOM objects
1638
    const maybe_querySelectorAll = (selector) => {
1639
 
1640
        if(typeof(selector) != 'string') {
1641
            if(selector instanceof Element) { // JS or jQuery
1642
                return [selector];
1643
            }
1644
            else {
1645
                let to_return = [];
1646
 
1647
                for(const obj of selector) {
1648
                    if(obj instanceof Element) {
1649
                        to_return.push(obj);
1650
                    }
1651
                }
1652
                return to_return;
1653
            }
1654
        }
1655
 
1656
        // clean problematic selectors
1657
        (selector.match(/(#[0-9][^\s:,]*)/g) || []).forEach(function(n) {
1658
            selector = selector.replace(n, '[id="' + n.replace("#", "") + '"]');
1659
        });
1660
 
1661
        return document.querySelectorAll(selector);
1662
    };
1663
 
1664
 
1665
})());