/** * A color picker based on the Beaver Builder Color Picker, * with support for accents */ (function( v, $, undefined ) { 'use strict'; var FLBuilderColorPresets = [], UA = navigator.userAgent.toLowerCase(); /** * @since 1.8.4 * @method flBuilderParseColorValue * @return {Array} */ flBuilderParseColorValue = function( val ) { var value = val.replace(/\s+/g, ''), alpha = ( value.indexOf('rgba') !== -1 ) ? parseFloat( value.replace(/^.*,(.+)\)/, '$1') * 100 ) : 100, rgba = ( alpha < 100 ) ? true : false; return { value: value, alpha: alpha, rgba: rgba }; } /** * Main color picker class for Beaver Builder's custom implementation. * * @class window.VAMTAM.ColorPicker * @param {Object} settings * @since 1.6.4 */ v.ColorPicker = function( settings ) { this._html = '
'; // default settings var defaults = { elements : null, color : '', mode : 'hsl', controls : { horiz : 's', // horizontal defaults to saturation vert : 'l', // vertical defaults to lightness strip : 'h' // right strip defaults to hue }, target : false, // a DOM element / jQuery selector that the element will be appended within. Only used when called on an input. width : 200, // the width of the collection of UI elements presets: [], labels : { colorPresets: 'Color Presets', colorPicker: 'Color Picker', placeholder: 'Paste color here...', removePresetConfirm: 'Are you sure?', noneColorSelected: 'None color selected.', alreadySaved: '%s is already a saved preset.', noPresets: 'Add a color preset first.', accentsTitle: 'Accent Colors', presetAdded: '%s added to presets!', } }; // setting plugin options this.options = $.extend( {}, defaults, settings ); // initialize the color picker single instance this._init(); }; v.ColorPicker.prototype = Object.create( FLBuilderColorPicker.prototype ); /** * Prototype for new instances. * * @since 1.6.4 * @property {Object} prototype */ v.ColorPicker.prototype = Object.assign( v.ColorPicker.prototype, { /** * Initializes this instance. * * @since 1.6.4 * @method _init * * ** MODIFIED ** */ _init: function(){ var self = this, el = $( self.options.elements ); this.el = el; el.on( 'change', function( e ) { FLBuilder.preview.delayPreview( e ); var accent = self._sanitizeAccent( e.target.value ); if ( accent ) { $( e.target ).parent().find( '.vamtam-color-picker-color' ).css( 'background', accent ); } } ); this._color = new Color( '#ff0000' ).setHSpace( self.options.mode ); // Set picker color presets FLBuilderColorPresets = this.options.presets; // appends color picker markup to the body // check if there's already a color picker instance if( $('html').hasClass( 'vamtam-color-picker-init' ) ){ self.picker = $( '.vamtam-color-picker-ui' ); } else { self.picker = $( this._html ).appendTo( 'body' ); } // Browsers / Versions // Feature detection doesn't work for these, and $.browser is deprecated if ( UA.indexOf('compatible') < 0 && UA.indexOf('khtml') < 0 && UA.match( /mozilla/ ) ) { self.picker.addClass( 'iris-mozilla' ); } // prep 'em for re-use self.controls = { square : self.picker.find( '.iris-square' ), squareDrag : self.picker.find( '.iris-square-value' ), horiz : self.picker.find( '.iris-square-horiz' ), vert : self.picker.find( '.iris-square-vert' ), strip : self.picker.find( '.iris-strip' ), stripSlider : self.picker.find( '.iris-strip .iris-slider-offset' ) }; // small sanity check - if we chose hsv, change default controls away from hsl if ( self.options.mode === 'hsv' && self._has('l', self.options.controls) ) { self.options.controls = self._defaultHSVControls; } else if ( self.options.mode === 'hsl' && self._has('v', self.options.controls) ) { self.options.controls = self._defaultHSLControls; } // store it. HSL gets squirrely self.hue = self._color.h(); this._setTemplates(); // COLOR PRESETS UI -------------------------------------// // cache reference to the picker wrapper this._ui = $( '.vamtam-color-picker-ui' ); this._iris = $( '.iris-picker' ); this._wrapper = $( 'body' ); if( !$('html').hasClass( 'vamtam-color-picker-init' ) ){ this._ui .prepend( this._hexHtml ) .append( this._presetsHtml ); } self.element = this._ui.find( '.vamtam-color-picker-input' ); self._initControls(); self.active = 'external'; //self._dimensions(); self._change(); // binds listeners to all color picker instances self._addInputListeners( self.element ); // build the presets UI this._buildUI(); // adds needed markup and bind functions to all color fields this._prepareColorFields(); // bind picker control events this._pickerControls(); // bind presets control events this._presetsControls(); // adds opacity/alpha support this._buildAlphaUI(); // now we know that the picker is already added to the body $('html').addClass( 'vamtam-color-picker-init' ); }, _sanitizeAccent: function( value ) { var accent = value.match( /^accent(\d)/ ); if ( accent ) { return this.options.labels.accents[ accent[1] ]; } return false; }, /** * @since 1.6.4 * @method _prepareColorFields * * ** MODIFIED ** */ _prepareColorFields: function(){ var self = this; // append presets initial html and trigger that toggles the picker self.el.each( function(){ var $this = $( this ), $colorValue = $this.val(), $colorTrigger = $this.parent().find( '.vamtam-color-picker-color' ), $parsedValue = flBuilderParseColorValue( $colorValue ), $bgColor = '', accent = self._sanitizeAccent( $parsedValue.value ); if( $colorValue ){ // set initial color, check for alpha support if ( $colorTrigger.hasClass('vamtam-color-picker-alpha-enabled') && $parsedValue.rgba ) { $bgColor = $this.val().toString(); } else if ( ! $colorTrigger.hasClass('vamtam-color-picker-alpha-enabled') && $parsedValue.rgba ) { var $newColorValue = $colorValue.replace('rgba', 'rgb') $newColorValue = $newColorValue.substr(0, $newColorValue.lastIndexOf(",")) + ')'; self._color._alpha = 1; $bgColor = $newColorValue; $this.val($newColorValue); } else { $bgColor = accent || $this.val().toString(); } $colorTrigger.css({ backgroundColor: $bgColor }); } }); }, /** * Sets templates to build the color picker markup. * * @since 1.6.4 * @method _setTemplates * * ** MODIFIED ** */ _setTemplates: function(){ this._alphaHtml = '
' + '
' + '
' + '
'; this._presetsHtml = '
' + '
' + '
' + this.options.labels.colorPresetsAccents + '
' + '
' + this.options.labels.colorPicker + '
' + '
' + '' + '
'; this._hexHtml = '' + '
'; this._presetsTpl = '
  • '; this._noPresetsTpl = '
  • ' + this.options.labels.noPresets + '
  • '; this._accentsTitleTpl = '
  • ' + this.options.labels.accentsTitle + '
  • '; this._presetsTitleTpl = '
  • ' + this.options.labels.colorPresets + '
  • '; }, /** * Builds the UI for color presets * * @see _addPresetView * @since 1.6.4 * @method _buildUI * * ** MODIFIED ** */ _buildUI: function(){ var self = this; self._presetsList = this._ui.find( '.vamtam-color-picker-presets-list' ); self._presetsList.html(''); // accents self._presetsList.append( this._accentsTitleTpl ); var accentsWrapper = document.createElement( 'li' ); accentsWrapper.classList.add( 'vamtam-color-picker-accents-list' ); for ( var i in this.options.labels.accents ) { var accent = this.options.labels.accents[ i ]; var s = document.createElement( 'span' ); s.innerHTML = i; s.style.background = accent; s.style.color = new Color( accent ).getMaxContrastColor().toString(); accentsWrapper.appendChild( s ); } self._presetsList.append( accentsWrapper ); // presets self._presetsList.append( this._presetsTitleTpl ); if( this.options.presets.length > 0 ){ $.each( this.options.presets, function( index, val ) { self._addPresetView( val ); }); } else { self._presetsList.append( this._noPresetsTpl ); } }, /** * Helper function to build a view for each color preset. * * @since 1.6.4 * @param {string} val the respective hex code for the color preset. * @return void * * ** MODIFIED ** */ _addPresetView: function( val ){ var hasEmpty = this._presetsList.find( '.vamtam-color-picker-no-preset' ); if( hasEmpty.length > 0 ){ hasEmpty.remove(); } var tpl = $( this._presetsTpl ), color = Color( val ); tpl .attr( 'data-color', val ) .find( '.vamtam-color-picker-preset-color' ) .css({ backgroundColor: color.toString() }) .end() .find( '.vamtam-color-picker-preset-label' ) .html( color.toString() ); this._presetsList.append( tpl ); }, /** * Shows a visual feedback when a color is added as a preset. * * @since 1.6.4 * @method _addPresetFeedback * * ** MODIFIED ** */ _addPresetFeedback: function(){ this._ui.append( '
    "' + this.options.labels.presetAdded.replace( '%s', this._color.toString() ) + '"
    ' ); this._ui .find( '.vamtam-color-picker-added' ) .hide() .fadeIn( 200 ) .delay( 2000 ) .fadeOut( 200, function(){ $(this).remove(); } ); }, /** * Sets some triggers for positioning logic of the picker and color reset. * * @since 1.6.4 * @method _pickerControls * * ** MODIFIED ** */ _pickerControls: function(){ var self = this; // logic for picker positioning this._wrapper .on( 'click', '.vamtam-color-picker-color', function(){ var $this = $(this); self._currentElement = $this.parent().find('.vamtam-color-picker-value'); self._ui.position({ my: 'left top', at: 'left bottom', of: $this, collision: 'flipfit', using: function( position, feedback ){ self._togglePicker( position ); } }) } ) .on( 'click', '.vamtam-color-picker-clear', function(){ var $this = $(this); self._currentElement = $this.parent().find('.vamtam-color-picker-value'); $this .prev( '.vamtam-color-picker-color' ) .css({ backgroundColor: 'transparent' }) .addClass('vamtam-color-picker-empty'); self._setColor( '' ); self.element.val( '' ); self._currentElement .val( '' ) .trigger( 'change' ); } ); // logic to hide picker when the user clicks outside it $( document ).on( 'click', function( event ) { if ( 0 === $( event.target ).closest( '.vamtam-color-picker-ui' ).length ) { $( '.vamtam-color-picker-ui.vamtam-color-picker-active' ).removeClass( 'vamtam-color-picker-active' ); } }); }, /** * Logic for color presets UI. * * @see _addPreset * @see _removePreset * @since 1.6.4 * @method _presetsControls * * ** MODIFIED ** */ _presetsControls: function(){ var self = this, addPreset = self._ui.find( '.vamtam-color-picker-preset-add' ), presets = self._ui.find( '.vamtam-color-picker-presets' ), presetsOpenLabel = presets.find( '.vamtam-color-picker-presets-open-label' ), presetsCloseLabel = presets.find( '.vamtam-color-picker-presets-close-label' ), presetsList = presets.find( '.vamtam-color-picker-presets-list' ); // add preset addPreset .off( 'click' ) .on( 'click', function(){ self._addPreset( self.element.val() ); } ); // presets toggle presetsList .css({ height: ( self.element.innerHeight() + self._iris.innerHeight() + 14 ) + 'px' }) .hide(); presets .off( 'click' ) .on( 'click', '.vamtam-color-picker-presets-toggle', function(){ presetsOpenLabel.toggleClass('vamtam-color-picker-active'); presetsCloseLabel.toggleClass('vamtam-color-picker-active'); presetsList.slideToggle( 500 ); } ) // set preset as current color .on( 'click', '.vamtam-color-picker-preset', function( e ){ var currentColor = new Color( $( this ).data( 'color' ).toString() ); self._setColor( currentColor ); self._currentElement .parent() .find( '.vamtam-color-picker-color' ) .css({ backgroundColor: currentColor.toString() }) .removeClass('vamtam-color-picker-empty'); presetsOpenLabel.toggleClass('vamtam-color-picker-active'); presetsCloseLabel.toggleClass('vamtam-color-picker-active'); presetsList.slideToggle( 500 ); }) // removes a preset .on( 'click', '.vamtam-color-picker-preset-remove', function( e ){ e.stopPropagation(); self._removePreset( $( this ).parent().data( 'color' ) ); }) // accents .on( 'click', '.vamtam-color-picker-accents-list span', function( e ) { e.stopPropagation(); self._setColor( 'accent' + e.target.innerHTML ); } ) }, /** * Removes a color preset from the array of presets and from the UI. * * @since 1.6.4 * @method _removePreset * @param {String} preset The respective hex value of the preset. * * ** MODIFIED ** */ _removePreset: function( preset ){ if( confirm( this.options.labels.removePresetConfirm ) ){ var color = preset.toString(), index = FLBuilderColorPresets.indexOf( color ); if( index > -1 ){ FLBuilderColorPresets.splice( index, 1 ); this.options.presets = FLBuilderColorPresets; this._presetsList .find('.vamtam-color-picker-preset[data-color="'+ color +'"]' ) .slideUp( function(){ $( this ).remove(); }); } if( FLBuilderColorPresets.length < 1 ){ this._presetsList.append( this._noPresetsTpl ); } // CALLBACK FOR PRESET REMOVED $(this).trigger( 'presetRemoved', { presets: FLBuilderColorPresets } ); } }, /** * Logic to add a color preset to the array of presets, and to the UI. * * @see _addPresetView * @see _addPresetFeedback * @method _addPreset * @param {String} preset The respective hex value of the preset. * @since 1.6.4 * * ** MODIFIED ** */ _addPreset: function( preset ){ var color = preset.toString(); // check if color is empty if( color === '' ){ alert( this.options.labels.noneColorSelected ); // check if the color is already added } else if( FLBuilderColorPresets.indexOf( color ) > -1 ){ alert( this.options.labels.alreadySaved.replace( '%s', '#' + color ) ); // add color to presets, fires visual feedback and triggers an event } else { this._addPresetView( color ); this._addPresetFeedback(); FLBuilderColorPresets.push( color ); this.options.presets = FLBuilderColorPresets; // CALLBACK FOR COLOR ADDED $(this).trigger( 'presetAdded', { presets: FLBuilderColorPresets } ); } }, /** * Logic for positioning of the color picker. * * @since 1.6.4 * @method _togglePicker * @param {Object} position An object containing x and y location for positioning. * * ** MODIFIED ** */ _togglePicker: function( position ){ var self = this; // logic for correct order of things if( this._ui.hasClass( 'vamtam-color-picker-active' ) ){ // if the picker is open, hides first, then changes the position this._ui.removeClass( 'vamtam-color-picker-active' ); if( position ){ setTimeout( function(){ self._ui.css( position ); self._ui.addClass( 'vamtam-color-picker-active' ); self._setColor( self._currentElement.val() ); }, 200 ); } } else { if( position ){ self._ui.css( position ); } // if the picker is closed, changes position first, then shows it setTimeout( function(){ self._ui.addClass( 'vamtam-color-picker-active' ); self._setColor( self._currentElement.val() ); }, 200 ); } }, /** * Logic to listen to events from the main color input and to bind it to the current color field. * * @see _setColor * @since 1.6.4 * @method _addInputListeners * @param {Object} input * * ** MODIFIED ** */ _addInputListeners: function( input ) { var self = this, debounceTimeout = 100, callback = function( event ){ var color = new Color( input.val() ), val = input.val(); input.removeClass( 'iris-error' ); // we gave a bad color if ( color.error ) { // don't error on an empty input - we want those allowed if ( val !== '' ) { input.addClass( 'iris-error' ); } } else { if ( color.toString() !== self._color.toString() ) { if( event.type === 'keyup' ){ if( val.match( /^[0-9a-fA-F]{3}$/ ) ) return; self._setColor( val ); self._currentElement .parent() .find( '.vamtam-color-picker-color' ) .css({ backgroundColor: Color( val ).toString() }) .removeClass( 'vamtam-color-picker-empty' ); self._currentElement .val( val ) .trigger( 'change' ); } else if( event.type === 'paste' ){ val = event.originalEvent.clipboardData.getData( 'text' ); hex = Color( val ).toString(); self._setColor( val ); input.val( hex ); self._currentElement .parent() .find( '.vamtam-color-picker-color' ) .css({ backgroundColor: hex }) .removeClass( 'vamtam-color-picker-empty' ); self._currentElement .val( val ) .trigger( 'change' ); return false; } } } }; input.on( 'change', callback ).on( 'keyup', self._debounce( callback, debounceTimeout ) ); }, /** * @since 1.6.4 * @method _setColor * @param {String} value * * ** MODIFIED ** */ _setColor: function( value ) { var self = this, oldValue = self.options.color, doDimensions = false, newColor, method; // ensure the new value is set. We can reset to oldValue if some check wasn't met. self.options.color = value; // cast to string in case we have a number value = '' + value; newColor = new Color( value ).setHSpace( self.options.mode ); if ( newColor.error ) { var accent = self._sanitizeAccent( value ); if ( accent ) { self._color = new Color( accent ).setHSpace( self.options.mode ); self.options.color = value; self.active = 'external'; self.usedAccent = value; self._change(); } else { self.options.color = oldValue; } } else { self._color = newColor; self.options.color = self._color.toString(); self.active = 'external'; self._change(); } }, /** * @since 1.6.4 * @method _change * * ** MODIFIED ** */ _change: function() { var self = this, controls = self.controls, color = self._getHSpaceColor(), actions = [ 'square', 'strip' ], controlOpts = self.options.controls, type = controlOpts[self.active] || 'external', oldHue = self.hue; if ( self.active === 'strip' ) { // take no action on any of the square sliders if we adjusted the strip actions = []; } else if ( self.active !== 'external' ) { // for non-strip, non-external, strip should never change actions.pop(); // conveniently the last item } $.each( actions, function(index, item) { var value, dimensions, cssObj; if ( item !== self.active ) { switch ( item ) { case 'strip': // reverse for hue value = ( controlOpts.strip === 'h' ) ? self._scale[controlOpts.strip] - color[controlOpts.strip] : color[controlOpts.strip]; controls.stripSlider.slider( 'value', value ); break; case 'square': dimensions = self._squareDimensions(); cssObj = { left: color[controlOpts.horiz] / self._scale[controlOpts.horiz] * dimensions.w, top: dimensions.h - ( color[controlOpts.vert] / self._scale[controlOpts.vert] * dimensions.h ) }; self.controls.squareDrag.css( cssObj ); break; } } }); // Ensure that we don't change hue if we triggered a hue reset if ( color.h !== oldHue && self._isNonHueControl( self.active, type ) ) { self._color.h(oldHue); } // store hue for repeating above check next time self.hue = self._color.h(); self.options.color = self._color.toString(); if ( self.element.is( ':input' ) && ! self._color.error ) { self.element.removeClass( 'iris-error' ); if ( self.element.val() !== self._color.toString() ) { self.element.val( self.usedAccent || self._color.toString() ); if ( this._currentElement ) { var bgColor = self._sanitizeAccent( self.usedAccent || '' ) || self._color.toString(); this._currentElement .val( self.usedAccent || self._color.toString() ) .parent() .find( '.vamtam-color-picker-color' ) .css({ backgroundColor: bgColor }) .removeClass( 'vamtam-color-picker-empty' ); self._wrapper.find( '.vamtam-alpha-slider-offset' ).css( 'background-color', bgColor ); this._currentElement.trigger( 'change' ); } } } self._paint(); self._inited = true; self.active = false; delete self.usedAccent; }, /** * Show an alpha UI when it is enabled * * @since 1.8.5 * @method _buildAlphaUI * * ** MODIFIED ** */ _buildAlphaUI: function() { var self = this; self._wrapper.on( 'click', '.vamtam-color-picker-color', function(){ var $this = $(this), $currentColor = self._currentElement.val(); if ( $this.hasClass('vamtam-color-picker-alpha-enabled') ) { // Add alpha if not exists if (self._ui.find('.vamtam-alpha-wrap').length <= 0) { $(self._alphaHtml).insertAfter( self._iris ); } self._pickerAlphaControls(); } else { self._ui.find('.vamtam-alpha-wrap').remove(); } }); }, /** * Enable the opacity/alpha to color picker * Credits to https://github.com/Codestar/codestar-wp-color-picker * * @since 1.8.5 * @method _pickerAlphaControls * * ** MODIFIED ** */ _pickerAlphaControls: function() { var self = this, el = self._currentElement, picker = flBuilderParseColorValue( el.val() ), floatValue = parseFloat( picker.alpha / 100 ), wrapper = self._wrapper, container = self._ui, alphaWrap = container.find('.vamtam-alpha-wrap'), alphaSlider = alphaWrap.find('.vamtam-alpha-slider'), alphaOffset = alphaWrap.find('.vamtam-alpha-slider-offset'), alphaHandle = alphaWrap.find('.ui-slider-handle'); // alpha slider alphaSlider.slider({ orientation: "horizontal", // slider: slide slide: function( event, ui ) { var slideValue = parseFloat( ui.value / 100 ); // update iris data alpha && color option && alpha text self._color._alpha = slideValue; self._change.apply( self, arguments ); }, // slider: create create: function() { // Initializes alpha values alphaOffset.css({ backgroundColor: picker.value }); // Clear alpha values wrapper.on('click', '.vamtam-color-picker-clear', function() { self._color._alpha = 1; alphaSlider.slider('value', 100).trigger('slide'); }); }, // slider: options value: picker.alpha, step: 1, min: 1, max: 100 }); }, } ); FLBuilder.addHook( 'settings-form-init', function( e ) { setTimeout( function() { var picker = new v.ColorPicker({ mode: 'hsv', elements: '.vamtam-color-picker-value', presets: FLBuilderConfig.colorPresets || [], labels: Object.assign( {}, FLBuilderStrings, VamtamColorPickerStrings ), mode: 'hsv' }); $( picker ).on( 'presetRemoved presetAdded', function( event, data ) { FLBuilder.ajax({ action: 'save_color_presets', presets: data.presets }); }); }, 0 ); } ); })( window.VAMTAM = window.VAMTAM || {}, jQuery );