MFColorPicker / 1.03 / js / mf-colorpicker.min.js
file last updated : 2024-03-23
/*! methodfish package release

 Project      : MFColorPicker
 Release      : 1.03
 Release date : 2024-03-21 16:39:10
 License      : MIT
 For more information please refer to https://methodfish.com/Projects/MFColorPicker

*/


/*
 Improved color selector; implement by including the js and then using the following html you get the
 standard html color picker, but this will also maintain the list of default colors and store them in
 a named element so that this can be re-used if desired.
 To assist in the UX, this class will also add a tick button to the right of the standard input (I find
 it wrong that the "Other" color selector doesn't have a OK button. Note, it would be good to add a cross,
 but this is not possible as the selector is a modal popup.
 The selector also maintains the list of colors including the latest color at the front of the list

 This class requires material-icons to have the tick work.

     <link rel="stylesheet preload prefetch" as="style" href="https://fonts.googleapis.com/icon?family=Material+Icons">

     <input type="hidden" id="mfpresets" value="ffffff,000000,ff0000,00f000,0000ff">
     <input type="color" class="mfcolor" onchange="alert()">

     <script>
         const _mfcolorpicker = new MFColorPicker();
         _mfcolorpicker.init('mfpresets');
     </script>

 */

const _MFC_COMPLEMENTARY='COMP';
const _MFC_ANALOGOUS = 'ANALO';
const _MFC_TRIADIC = 'TRIADIC';
const _MFC_SPLITCOMP = 'SPLIT';
function MFColorPicker() {

    var _gridSize=150;
    var _csvPresetElemId;
    var _selectedColor;
    var _selectedv;
    var _transparency;
    var _darkness;
    var _targetV0;
    var _target;
    var _activeMFColorPicker=false;
    //--------------------------------------
    function showColorPicker(target, but, presetId, n) {

        _target = target;
        _targetV0 = target.value;

        document.querySelectorAll('.mfcolorpicker-container').forEach(function(el) {
            el.remove();
        });


        if ( n==undefined ) n=_gridSize;
        let container = document.querySelector('.container');
        if ( !container ) {
            container           = document.createElement('div');
            container.className = 'mfcolorpicker-container removeOnEscape removeOnResize';
        }
        container.style.left = but.getBoundingClientRect().left+'px';
        container.style.top = (but.getBoundingClientRect().bottom + window.pageYOffset) + 'px';
        container.style.zIndex = getMaxZIndex()+1;
        document.body.appendChild(container);


        // ------------ color picker
        container.appendChild(getColorPicker(n));


        let bottom = document.createElement('div');
        bottom.style.padding='20px';
        bottom.style.margin='0';
        bottom.style.borderTop='solid 1px #dcd8dc';
        container.appendChild(bottom);

        let v = colToRGBA(_target.value); // 'rgba(0,0,0,1)';

        // ------------ darkness
        let darknessLine            = document.createElement('div');
        darknessLine.style.padding='0';
        darknessLine.style.margin='0';
        bottom.appendChild(darknessLine);
        if ( 1 ) {

            let darkness       = document.createElement('input');
            darkness.className = 'mfcolorpicker-darkness';
            darkness.title = 'Color darkness';
            darkness.type      = 'range';
            darkness.setAttribute('min', -255);
            darkness.setAttribute('max', 255);
            darkness.setAttribute('step', 1);
            darkness.value = 0;
            darknessLine.appendChild(darkness);
            _darkness = darkness;


            darkness.addEventListener('input', function (e) {
                let amt = e.target.value;
                let rgb = adjustColorBrightness(e.target.getAttribute('data-orig'), e.target.value);
                let alpha = _transparency.value;
                let rgba = rgb.replace(')', `,${alpha})`).replace(/rgb\(/, 'rgba(');
                setSelectedColor(rgba);
                setSelectedColor(rgba);
                setSelectedTarget(_target, rgba);
            });

            darkness.style.background = 'linear-gradient(to right, black, '+v+', white)';
            darkness.setAttribute('data-orig', v);
        }



        // ------------ transparency
        let transparencyLine            = document.createElement('div');
        transparencyLine.className = ''
        transparencyLine.style.padding='0';
        transparencyLine.style.margin='0';
        bottom.appendChild(transparencyLine);
        if ( 1 ) {

            let transparencySlider            = document.createElement('div');
            transparencySlider.className = 'mfcolorpicker-checkerboard mfcolorpicker-checkerboard-wrapper'
            transparencySlider.style.padding='0';
            transparencySlider.style.margin='0';
            transparencySlider.title = 'Color transparency';
            transparencyLine.appendChild(transparencySlider);

            let transparencyChecker = document.createElement('div');
            transparencyChecker.className = 'mfcolorpicker-checkerboard-underlay';
            transparencySlider.appendChild(transparencyChecker);

            let transparency       = document.createElement('input');
            transparency.className = 'mfcolorpicker-transparency';
            transparency.type      = 'range';
            transparency.setAttribute('min', 0);
            transparency.setAttribute('max', 1);
            transparency.setAttribute('step', 0.01);
            transparencySlider.appendChild(transparency);
            _transparency = transparency;


            transparency.addEventListener('input', function () {
                let picker              = document.querySelector('.mfcolorpicker-picker');
                picker.style.opacity    = transparency.value;
                transparencyv.innerHTML = (parseFloat(transparency.value) * 100) + '%';

                let rgb = _selectedColor.getAttribute('data-selected');
                if (rgb.startsWith('#')) rgb = hexToRGBA(rgb);
                let parts                            = rgb.substring(rgb.indexOf('(')+1).split(',');
                let rgba                             = `rgba(${parts[0].trim()}, ${parts[1].trim()}, ${parts[2].trim()}, ${transparency.value})`;
                _selectedColor.style.backgroundColor = rgba;


                // let selectedValue = document.querySelector('.mfcolorpicker-selectedvalue');
                // if (selectedValue.getAttribute('data-type') == 'hex') {
                //     let hex                 = rgbaToHex(rgba);
                //     selectedValue.innerHTML = hex;
                // }
                // else {
                //     selectedValue.innerHTML = rgba;
                // }
                setSelectedColor(rgba);
                setSelectedColor(rgba);
                setSelectedTarget(_target, rgba);
            });


            let transparencyv       = document.createElement('span');
            transparencyv.className = 'mfcolorpicker-transparencyvalue';
            transparencyv.innerHTML = '100%';
            transparencyLine.appendChild(transparencyv);
        }


        // ------------ eye dropper
        if (window.EyeDropper) { // this only works on https domains
            let eyedropperButton       = document.createElement('i');
            eyedropperButton.className = 'material-icons mfcolorpicker-eyedropper';
            eyedropperButton.innerHTML = 'colorize';
            bottom.appendChild(eyedropperButton);
            eyedropperButton.addEventListener('click', async () => {
                const eyeDropper = new EyeDropper();

                eyeDropper
                    .open()
                    .then((result) => {
                        let rgba = colToRGBA(result.sRGBHex+'ff');
                        setSelectedColor(rgba);
                        setSelectedTarget(_target, rgba);
                    })
                    .catch((e) => {
                        console.error(e);
                    });

            });
        }
        else {
            console.warn('No EyeDropper available on this browser');
        }


        // ------------ selected color sample
        let selectedContainer = document.createElement('div');
        selectedContainer.className = 'mfcolorpicker-presets-pair mfcolorpicker-selected-color';
        bottom.appendChild(selectedContainer);
        if ( 1 ) {
            let selectedColorBg       = document.createElement('div');
            selectedColorBg.className = 'mfcolorpicker-selected-bg mfcolorpicker-checkerboard';
            selectedContainer.appendChild(selectedColorBg);


            _selectedColor           = document.createElement('div');
            _selectedColor.className = 'mfcolorpicker-selected';
            selectedContainer.appendChild(_selectedColor);
        }




        // ------------ selectedvalue text
        let selectedv           = document.createElement('span');
        selectedv.className = 'mfcolorpicker-selectedvalue';
        bottom.appendChild(selectedv);
        selectedv.addEventListener('click', function() {
            if ( selectedv.getAttribute('data-type')=='hex' ) selectedv.setAttribute('data-type','rgb');
            else selectedv.setAttribute('data-type','hex');

            showSelectedValue( _selectedColor.getAttribute('data-selected'));
        });
        _selectedv = selectedv;



        // ------------ complementary colors
        bottom.appendChild(getCompsContainer());



        // ------------ presets group
        let presets = getPresets(presetId);
        if ( presets ) bottom.appendChild(presets);
        setSelectedColor(v);



        let buttons           = document.createElement('div');
        buttons.className = 'mfcolorpicker-buttons';
        bottom.appendChild(buttons);



        let ok           = document.createElement('button');
        ok.className = 'mfcolorpicker-button';
        ok.innerHTML='OK';
        buttons.appendChild(ok);
        ok.addEventListener('click', function() {
            let v= _selectedColor.getAttribute('data-selected');
            setSelectedTarget(_target, v);
            triggerElementEvent(_target, 'change');
            container.remove();
        })


        let cancel           = document.createElement('button');
        cancel.className = 'mfcolorpicker-button';
        cancel.innerHTML='Cancel';
        buttons.appendChild(cancel);
        cancel.addEventListener('click', function() {
            setSelectedColor(_targetV0);
            setSelectedTarget(_target, _targetV0);
            container.remove();
        })

        moveDivOnscreen(container);
        _activeMFColorPicker = true;


        document.querySelector('.mfcolorpicker-comps-container > SELECT').addEventListener('change', showCompsPatch);
        document.querySelector('.mfcolorpicker-comps-container > SELECT').addEventListener('input', showCompsPatch);

        setTimeout(function() {
            showCompsPatch();
        },1);

    }
    //--------------------------------------
    function adjustColorBrightness(rgb, darkness) {
        let parts=rgb.substring(rgb.indexOf('(')+1).split(',');
        darkness = parseInt(darkness);
        let r = clamp(parseInt(parts[0]) + darkness, 0, 255);
        let g = clamp(parseInt(parts[1]) + darkness, 0, 255);
        let b = clamp(parseInt(parts[2]) + darkness, 0, 255);
        let result =`rgb(${r}, ${g}, ${b})`;
        return result;
    }
    function clamp(value, min, max) {
        return Math.min(Math.max(value, min), max);
    }
    //--------------------------------------
    function getMfDefaultColList(presetId) {
        if ( presetId ) {
            let el = document.querySelector('#' + presetId);
            if (el) return el.value;
        }
        return 'ffffff,000000,ababab,d7d7d7,d7d7d7,e6dee0,c00000,ea0000,ff0505,ff5001,ed7d31,375623,548235,00b050,a9d18e,e2f0d9';
    }
    //--------------------------------------
    function isNumeric(n) {
        return !isNaN(parseFloat(n)) && isFinite(n);
    }
    //--------------------------------------
    function getMaxZIndex() {
        let elems= document.querySelectorAll('div');
        let max=0;
        for(let i=0;i<elems.length;i++) {
            let t=getComputed(elems[i],'z-index');
            if ( t!='auto' ) {
                if (isNumeric(t) && parseInt(t) > max) {
                    max = parseInt(t);
                }
            }
        }

        elems=document.getElementsByTagName('canvas');
        for(let i=0;i<elems.length;i++) {
            let t=getComputed(elems[i],'z-index'); // elems[i].style.zIndex;
            if ( isNumeric(t) && parseInt(t)>max ) {
                max=parseInt(t);
            }
        }
        return parseInt(max);
    }
    //--------------------------------------
    function getComputed(elem, rule) {
        if (!elem) return null;
        let styles = window.getComputedStyle(elem);
        let result = styles.getPropertyValue(rule);
        return result;
    }
    //--------------------------------------
    function colToHex(v) {
        if ( v.startsWith('#')) return v;
        else return rgbaToHex(v);
    }
    //--------------------------------------
    function colToRGBA(v) {
        if ( v.startsWith('rgba') ) return v;
        if ( v.startsWith('rgb') ) {
            return v.replace(')', ', 1)');
        }
        return hexToRGBA(v);
    }
    //--------------------------------------
    function getCompsContainer() {
        let compsContainer           = document.createElement('span');
        compsContainer.className = 'mfcolorpicker-comps-container';

        let selector       = document.createElement('SELECT');
        selector.innerHTML = '<option value="COMP">Complementary</option>'
                             + '<option value="ANALO">Analogous</option>'
                             + '<option value="TRIADIC">Triadic</option>'
                             + '<option value="SPLIT" title="Split-complementary">Split-comp</option>';
        compsContainer.appendChild(selector);

        let colors       = document.createElement('DIV');
        colors.className = 'mfcolorpicker-colorblock';
        compsContainer.appendChild(colors);

        return compsContainer;
    }
    function showCompsPatch() {
        if ( !document.querySelector('.mfcolorpicker-comps-container > SELECT')) return;
        let typeCd = document.querySelector('.mfcolorpicker-comps-container > SELECT').value;
        let rgba= document.querySelector('.mfcolorpicker-selected').style.backgroundColor;
        let colorblock= document.querySelector('.mfcolorpicker-comps-container > .mfcolorpicker-colorblock');

        let samples = document.querySelectorAll('.mfcolorpicker-colorblock .sample');
        if ( samples ) samples.forEach(function(sample) { sample.remove()});

        let sample = document.createElement('div');
        sample.className = 'sample';
        sample.style.backgroundColor = rgba;
        sample.title = rgba;
        colorblock.appendChild(sample);

        let colors = getComps(rgba, typeCd);
        for(let c=0; c<colors.length; c++) {
            rgba = colors[c];
            let sample = document.createElement('div');
            sample.className = 'sample';
            sample.style.backgroundColor = rgba;
            sample.title = rgba;
            colorblock.appendChild(sample);
            sample.addEventListener('click', function (e) {
                e.preventDefault();
                e.stopPropagation();
                let rgba        = e.target.style.backgroundColor;
                if ( rgba.startsWith('rgb(')) rgba='rgba('+rgba.substring(4, rgba.length-1)+', 1)';
                setSelectedColor(rgba);
                let darkness = document.querySelector('.mfcolorpicker-darkness');
                darkness.style.background = 'linear-gradient(to right, black, '+rgba+', white)';
                darkness.setAttribute('data-orig', rgba);
                darkness.value = 0;
            });
        }
    }
    function getComps(rgbaColor, typeCd) {
        const colors = [];

        // Parse the RGBA color
        let [r, g, b, a] = rgbaColor.match(/(\d+(\.\d+)?)/g).map(Number);
        if ( a==undefined ) a=1;

        // Complementary color
        switch (typeCd) {
            case _MFC_COMPLEMENTARY:
                const complementaryR = 255 - r;
                const complementaryG = 255 - g;
                const complementaryB = 255 - b;
                colors.push(`rgba(${complementaryR}, ${complementaryG}, ${complementaryB}, ${a})`);
                break;

            case _MFC_ANALOGOUS:
                // Analogous colors
                const analogous1 = rotateHue(r, g, b, -30);
                const analogous2 = rotateHue(r, g, b, 30);
                colors.push(`rgba(${analogous1[0]}, ${analogous1[1]}, ${analogous1[2]}, ${a})`);
                colors.push(`rgba(${analogous2[0]}, ${analogous2[1]}, ${analogous2[2]}, ${a})`);
                break;

            case _MFC_TRIADIC:
                // Triadic colors
                const triadic1 = rotateHue(r, g, b, 120);
                const triadic2 = rotateHue(r, g, b, 240);
                colors.push(`rgba(${triadic1[0]}, ${triadic1[1]}, ${triadic1[2]}, ${a})`);
                colors.push(`rgba(${triadic2[0]}, ${triadic2[1]}, ${triadic2[2]}, ${a})`);
                break;

            case _MFC_SPLITCOMP:
                // Split-complementary colors
                const splitComplementary1 = rotateHue(r, g, b, 72); // -150);
                const splitComplementary2 = rotateHue(r, g, b, 216); // -30);
                colors.push(`rgba(${splitComplementary1[0]}, ${splitComplementary1[1]}, ${splitComplementary1[2]}, ${a})`);
                colors.push(`rgba(${splitComplementary2[0]}, ${splitComplementary2[1]}, ${splitComplementary2[2]}, ${a})`);
                break;

            default:
                console.error('Invalid type code give '+typeCd);
        }
        return colors;
    }
    function rotateHue(r, g, b, angle) {
        // Convert RGB to HSL
        let hsl = rgbToHsl(r, g, b);
        // Rotate the hue
        hsl[0] += angle / 360;
        // Convert back to RGB
        let rgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
        return rgb;
    }
    function rgbToHsl(r, g, b) {
        r /= 255, g /= 255, b /= 255;
        let max = Math.max(r, g, b), min = Math.min(r, g, b);
        let h, s, l = (max + min) / 2;

        if (max == min) {
            h = s = 0; // achromatic
        } else {
            let d = max - min;
            s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
            switch (max) {
                case r: h = (g - b) / d + (g < b ? 6 : 0); break;
                case g: h = (b - r) / d + 2; break;
                case b: h = (r - g) / d + 4; break;
            }
            h /= 6;
        }

        return [h, s, l];
    }
    function hslToRgb(h, s, l) {
        let r, g, b;

        if (s == 0) {
            r = g = b = l; // achromatic
        } else {
            function hue2rgb(p, q, t) {
                if (t < 0) t += 1;
                if (t > 1) t -= 1;
                if (t < 1 / 6) return p + (q - p) * 6 * t;
                if (t < 1 / 2) return q;
                if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
                return p;
            }

            let q = l < 0.5 ? l * (1 + s) : l + s - l * s;
            let p = 2 * l - q;
            r = hue2rgb(p, q, h + 1 / 3);
            g = hue2rgb(p, q, h);
            b = hue2rgb(p, q, h - 1 / 3);
        }

        return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
    }
    //--------------------------------------
    function getPresets(presetId) {
        if ( !document.querySelector('#'+presetId) ) return null;
        var presets           = document.createElement('div');
        presets.className = 'mfcolorpicker-presets-container';
        let presetCols=document.querySelector('#'+presetId).value.replace(/#/g,'').split(',');
        presetCols.forEach(function(optv) {

            let presetsColorPair = document.createElement('div');
            presetsColorPair.className = 'mfcolorpicker-presets-pair preset';
            presets.appendChild(presetsColorPair);


            let presetColorBg = document.createElement('div');
            presetColorBg.className = 'mfcolorpicker-preset mfcolorpicker-checkerboard';
            presetsColorPair.appendChild(presetColorBg);


            let presetColor           = document.createElement('div');
            presetColor.className = 'mfcolorpicker-preset';
            let c = colToRGBA('#'+optv);
            presetColor.style.backgroundColor = c;
            presetsColorPair.appendChild(presetColor);


            presetColor.addEventListener('click', function() {
                let rgba = colToRGBA(optv);
                setSelectedColor(rgba);
                setSelectedTarget(_target, rgba);

                let parts = rgba.replace(/rgba\(/, '').split(',');
                let rgb = `rgb(${parseInt(parts[0])}, ${parseInt(parts[1])}, ${parseInt(parts[2])})`;
                let darkness = document.querySelector('.mfcolorpicker-darkness');
                darkness.style.background = 'linear-gradient(to right, black, '+rgb+', white)';
                darkness.setAttribute('data-orig', rgb);
                darkness.value=0;
            })
        });

        return presets;
    }
    //-----------------------------------------------------------------------------------------
    function triggerElementEvent(elem, eventType) {
        let elems=[elem];
        for(let i=0;i<elems.length; i++) {
            if (eventType == undefined) eventType = 'change';
            if ("createEvent" in document) {
                let evt = document.createEvent("HTMLEvents");
                evt.initEvent(eventType, true, true);
                if ( elems[i] ) elems[i].dispatchEvent(evt); // trigger Events
            }
            else {
                elems[i].fireEvent("on" + eventType);
            }
        }
    }
    //--------------------------------------
    function getColorPicker(n) {
        let picker = document.querySelector('.mfcolorpicker-picker');
        if (!picker) {
            picker           = document.createElement('div');
            picker.className = 'mfcolorpicker-picker';
        }


        picker.style.gridTemplateColumns = `repeat(${n}, 1fr)`;
        picker.style.gridTemplateRows    = `repeat(${n}, 1fr)`;


        for (let i = 0; i < n; i++) {
            for (let j = 0; j < n; j++) {
                const brightness   = (i / (n - 1)) * 100; // Calculate brightness from top to bottom
                const hue          = (j / (n - 1)) * 360; // Calculate hue from left to right
                const saturation   = 100;
                const transparency = 1;
                const color        = `hsla(${hue}, ${saturation}%, ${brightness}%, ${transparency})`;
                const colorBox     = document.createElement('div');
                colorBox.classList.add('mfcolorpicker-color-box');
                colorBox.style.backgroundColor = color;
                picker.appendChild(colorBox);


                colorBox.addEventListener('mouseover', function (e) {
                    e.preventDefault();
                    e.stopPropagation();
                    const regex                          = /rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/;
                    const v                              = e.target.style.backgroundColor;
                    const [, r, g, b]                    = v.match(regex) || [];
                    let rgb                              = v;
                    let transparency                     = _transparency.value;
                    let rgba                             = rgb.replace('rgb', 'rgba').replace(')', `, ${transparency})`);
                    _selectedColor.style.backgroundColor = rgba;
                    showSelectedValue(rgba);

                    setSelectedTarget(_target, rgba);
                });


                colorBox.addEventListener('click', function (e) {
                    e.preventDefault();
                    e.stopPropagation();
                    document.querySelector('.mfcolorpicker-darkness').value = 0;
                    let transparency = _transparency.value;
                    const rgb        = e.target.style.backgroundColor;
                    let rgba         = rgb.replace('rgb', 'rgba').replace(')', `, ${transparency})`);
                    setSelectedColor(rgba);

                    let darkness = document.querySelector('.mfcolorpicker-darkness');
                    darkness.style.background = 'linear-gradient(to right, black, '+rgba+', white)';
                    darkness.setAttribute('data-orig', rgba);
                    //console.log('----------------------------------- Click selected '+rgba);
                });

            }
        }


        picker.addEventListener('mouseout', function (e) {
            let v = _selectedColor.getAttribute('data-selected');
            if (v) {
                //console.log('\n\n\n--------------------------------- Switching back to '+v);
                let rgba= v;
                _selectedColor.style.backgroundColor = rgba;

                showSelectedValue(rgba);
                setSelectedColor(rgba);
                setSelectedTarget(_target, rgba);

            }
        });


        return picker;
    }
    //-----------------------------------------------------------------------------------------
    function getElemInfo(elem) {
        if ( !elem ) return '[null]';
        let result = elem.tagName;
        if (elem.id) result += '#' + elem.id;
        if (elem.className) result += '.' + elem.className;
        if ( 1 ) {
            result+=' tx='+elem.innerText.substring(0, 20).trim().replace(/\n/g, '{LF}');
            if ( elem.innerText.length>20 ) result+='...';
        }

        return '[' + result + ']';
    }
    //--------------------------------------
    function getAlpha(rgba) {
        let match = rgba.match(/rgba?\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)/);
        if (match) {
            // Extract the alpha value and convert it to a percentage
            let alpha = parseFloat(match[4]);
            let alphaPercentage = alpha * 100;
            return alphaPercentage;
        } else {
            return null; // Return null if the background color is not in RGBA format
        }
    }
    //--------------------------------------
    function hexToRGBA(hex) {
        if ( hex.startsWith('rgb')) return colToRGBA(hex);
        hex = hex.replace(/^#/, '');// Remove # if present
        if ( hex.length==6) hex+='FF';

        // Parse hexadecimal string to RGB values
        let r = parseInt(hex.substring(0, 2), 16);
        let g = parseInt(hex.substring(2, 4), 16);
        let b = parseInt(hex.substring(4, 6), 16);
        let a = parseInt(parseInt(hex.substring(6, 8), 16) / 255 * 100)/100; // Convert alpha from [0, 255] to [0, 1]

        // Return the RGBA values as an object
        return `rgba(${r}, ${g}, ${b}, ${a})`;
    }
    //--------------------------------------
    function setSelectedColor(v) {
        if ( _selectedColor ) {
            _selectedColor.style.backgroundColor = v;
            _selectedColor.setAttribute('data-selected', v);

            let parts = v.split(',');
            parts[3] = "0)"; // Set the alpha value to 0
            let vx = parts.join(',');

            parts[3] = "1)"; // Set the alpha value to 0
            let vxx = parts.join(',');

            _transparency.style.background = `linear-gradient(to right, ${vx}, ${vxx})`;

            showSelectedValue(v);
            _transparency.value = getAlpha(v) / 100;

            let picker = document.querySelector('.mfcolorpicker-picker');
            if (picker) {
                picker.style.opacity  = _transparency.value;
                document.querySelector('.mfcolorpicker-transparencyvalue').innerHTML = parseInt(parseFloat(_transparency.value) * 100) + '%';
            }
        }
    }
    //--------------------------------------
    function showSelectedValue(v) {
        if (_selectedv.getAttribute('data-type') == 'hex') {
            _selectedv.innerHTML = rgbaToHex(v);
        }
        else {
            _selectedv.innerHTML = v;
        }
        if ( _selectedv.innerHTML.indexOf('NaN')>-1 ) debugger;

        showCompsPatch();
    }
    //--------------------------------------
    function rgbaToHex(rgba) {
        if ( !rgba ) debugger;
        if ( rgba.startsWith('#')) return rgba;
        if (rgba) {
            let parts = rgba.substring(5, rgba.length - 1).split(",");
            let r = parseInt(parts[0]);
            let g = parseInt(parts[1]);
            let b = parseInt(parts[2]);
            let a = 1;
            if ( parts.length>2 ) a = parseFloat(parts[3]); // Parse alpha as float

            // Convert alpha value to integer (0-255) before converting to hex
            let alphaHex = Math.round(a * 255).toString(16).padStart(2, '0');

            return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b) + alphaHex;
        }
        return ''; // Return empty string if rgba is empty
    }
    //--------------------------------------
    function componentToHex(c) {
        let hex = c.toString(16);
        return hex.length == 1 ? "0" + hex : hex;
    }
    //--------------------------------------
    function initMfColorButton(elem, presetId) {

        elem.style.display='none';
        elem.addEventListener('change', function() {
            mfSaveColor(elem);
        });


        if ( presetId ) elem.setAttribute('list', presetId);


        var pair = document.createElement('div');
        pair.className= 'mfcolorpicker-presets-pair mfcolorpicker-current-color '+elem.className;
        removeClassName(pair, 'hidden');
        elem.parentNode.insertBefore(pair, elem.nextElementSibling);
        if ( 1 ) {

            var bg       = document.createElement('div');
            bg.className = 'mfcolorpicker-color-button mfcolorpicker-checkerboard';
            pair.appendChild(bg);

            var but   = document.createElement('div');
            but.className             = 'mfcolorpicker-color-button';
            but.innerHTML             = ' ';
            but.style.backgroundColor = elem.value;
            pair.appendChild(but);

            but.addEventListener('click', function () {
                showColorPicker(elem, but, presetId);
            });
        }
    }
    //------------------------------------------------------------------------------------
    function removeClassName(elem, classNm) {
        if (!elem) return;
        const regex    = new RegExp('\\b' + classNm + '\\b', 'g');
        elem.className = elem.className.replace(regex, '').trim();
    }
    //------------------------------------------------------------------------------------
    function moveDivOnscreen(elem, container) {
        let viewportWidth;
        let containerLeft = 0;
        let adj = 5;
        let scrollX = window.scrollX || window.pageXOffset; // Get the horizontal scroll position
        let scrollY = window.scrollY || window.pageYOffset; // Get the vertical scroll position

        if (container === undefined) container = window;
        if (container === window) viewportWidth = window.innerWidth - adj;
        else {
            containerLeft = container.getBoundingClientRect().left;
            viewportWidth = container.getBoundingClientRect().width - adj;
        }

        let viewportHeight;
        if (container === window) viewportHeight = window.innerHeight - adj;
        else viewportHeight = container.getBoundingClientRect().height - adj;

        // Get the bounding rectangle of the element
        const elemRect = elem.getBoundingClientRect();

        // Calculate the desired position of the element relative to the viewport
        let newTop = (elemRect.top + window.pageYOffset + adj); // Maintain the current top position
        let newLeft = (elemRect.left + window.pageXOffset); // Maintain the current left position

        // Adjust the left position if the element exceeds the viewport width
        if ((elemRect.right - containerLeft) > viewportWidth) {
            newLeft -= (elemRect.right - containerLeft - viewportWidth + 2*adj);
        }

        // Adjust the top position if the element exceeds the viewport height
        if (elemRect.bottom > viewportHeight) {
            newTop -= (elemRect.bottom - viewportHeight + 2*adj);
        }

        // Adjust the top position if the element's top is above the viewport
        if (elemRect.top < 0) {
            newTop -= (elemRect.top - scrollY + 2*adj);
        }

        // Set the new position of the element
        elem.style.top = newTop + 'px';
        elem.style.left = newLeft + 'px';
    }
    //---------------------------------------
    function mfSaveColor(elem) {
        var listElem = document.querySelector('#'+_csvPresetElemId);
        
        // Add the new colour to the csv list
        if (listElem) {
            var csv  = listElem.value;
            var vals  = csv.split(',');
            var col = elem.value.replace(/#/,'');
            if (!vals.includes(col)) {
                if (elem.value != '') {
                    listElem.value = col + ',' + csv;
                    doBuildListElem();
                }
            }
        }
    }
    //---------------------------------------
    function doBuildListElem() {
        var list = document.querySelector('#'+_csvPresetElemId+'list');
        if (!list) {
            list    = document.createElement('datalist');
            list.id = _csvPresetElemId+'list';
            document.body.appendChild(list);
        }

        var cols = getMfDefaultColList(_csvPresetElemId).split(',');
        list.innerHTML='';
        for (var c = 0; c < cols.length; c++) {
            list.innerHTML += '<option>#' + cols[c].toUpperCase() + '</option>';
        } 
    }
    //------------------------------------------------------------------------------------
    function addClassName(elem, classNm) {
        if ( elem.id == 'wysiwyg' ) debugger;
        const regex = new RegExp('\\b' + classNm + '\\b', 'g');
        if (!regex.test(elem.className)) elem.className += ' ' + classNm;
    }
    //---------------------------------------
    function hasClassName(elem, classNm) {
        const regex = new RegExp('\\b' + classNm + '\\b');
        return regex.test(elem.classList);
    }
    //---------------------------------------
    function setSelectedTarget(target, v) {
        if ( !target ) return;
        target.type='text';
        let hex = colToHex(v);
        target.value = hex;
        target.nextElementSibling.children[1].style.backgroundColor=hexToRGBA(hex);
        triggerElementEvent(_target, 'input');

        showCompsPatch();
    }
    //------------------------------------------------------------------------------------
    function getParentElem(elem, classNm) {
        while (elem) {
            if (hasClassName(elem, classNm)) return elem;
            elem = elem.parentElement;
        }
        return elem;
    }
    //---------------------------------------
    return {
        colToHex(v){
            return colToHex(v);
        }
        ,colToRGBA(v) {
            return colToRGBA(v);
        }
        ,setSelectedTarget(target, v) {
            setSelectedTarget(target, v);
        }
        ,init(csvPresetElemId, gridSize) {

            if ( gridSize!=undefined ) _gridSize = gridSize;
            _csvPresetElemId = csvPresetElemId;
            doBuildListElem();


            var inputs = document.querySelectorAll('input.mfcolorpicker');
            for (var i = 0; i < inputs.length; i++) {
                if ( !hasClassName(inputs[i], 'mfcolorpicker-ready')) {
                    inputs[i].className+=' mfcolorpicker-ready';
                    if ( !inputs[i].value ) inputs[i].value='#ffffff';
                    initMfColorButton(inputs[i], csvPresetElemId);
                }
            }


            if ( !hasClassName(document.body, 'mfcolorpicker-events') ) {
                addClassName(document.body, 'mfcolorpicker-events');

                window.addEventListener('click', function (e) {
                    if (e.target) {
                        if (e.target.className && hasClassName(e.target, 'removeOnEscape')) return;
                        if (e.target.className && hasClassName(e.target, 'mfcolorpicker-color-button')) return;
                        if (getParentElem(e.target, 'mfcolorpicker-container') != null) return;
                    }
                    if (_activeMFColorPicker) {
                        setSelectedColor(_targetV0);
                        setSelectedTarget(_target, _targetV0);

                        document.querySelectorAll('.removeOnEscape').forEach(function (el) {
                            if (el.close) el.close;
                            else el.remove();
                        });
                    }
                });


                window.addEventListener('resize', event => {
                    setSelectedColor(_targetV0);
                    if (_target) setSelectedTarget(_target, _targetV0);

                    document.querySelectorAll('.removeOnResize').forEach(function (el) {
                        el.remove();
                        _activeMFColorPicker = false;
                    });
                });


                window.addEventListener('keydown', event => {
                    if (event.key === 'Escape') {
                        if (window.EyeDropper) {
                            const eyeDropper = new EyeDropper();
                            eyeDropper.close();
                        }


                        setSelectedColor(_targetV0);
                        setSelectedTarget(_target, _targetV0);

                        document.querySelectorAll('.removeOnEscape').forEach(function (el) {
                            if (el.close) el.close;
                            else el.remove();
                        });
                        _activeMFColorPicker = false;
                    }
                });
            }
        }
    }
}

About

License

Latest Release

Version 1.032024-05-08