/*! * Bootstrap Colorpicker * http://mjolnic.github.io/bootstrap-colorpicker/ * * Originally written by (c) 2012 Stefan Petre * Licensed under the Apache License v2.0 * http://www.apache.org/licenses/LICENSE-2.0.txt * * @todo Update DOCS */ (function ($) { // Color object const Color = function (val) { this.value = { h: 0, s: 0, b: 0, a: 1, }; this.origFormat = null; // original string format if (val) { if (val.toLowerCase !== undefined) { this.setColor(val); } else if (val.h !== undefined) { this.value = val; } } }; Color.prototype = { constructor: Color, _sanitizeNumber(val) { if (typeof val === 'number') { return val; } if (isNaN(val) || (val === null) || (val === '') || (val === undefined)) { return 1; } if (val.toLowerCase !== undefined) { return parseFloat(val); } return 1; }, // parse a string to HSB setColor(strVal) { strVal = strVal.toLowerCase(); this.value = this.stringToHSB(strVal) || { h: 0, s: 0, b: 0, a: 1, }; }, stringToHSB(strVal) { strVal = strVal.toLowerCase(); const that = this; let result = false; $.each(this.stringParsers, (i, parser) => { const match = parser.re.exec(strVal); const values = match && parser.parse.apply(that, [match]); const format = parser.format || 'rgba'; if (values) { if (format.match(/hsla?/)) { result = that.RGBtoHSB.apply(that, that.HSLtoRGB.apply(that, values)); } else { result = that.RGBtoHSB.apply(that, values); } that.origFormat = format; return false; } return true; }); return result; }, setHue(h) { this.value.h = 1 - h; }, setSaturation(s) { this.value.s = s; }, setBrightness(b) { this.value.b = 1 - b; }, setAlpha(a) { this.value.a = parseInt((1 - a) * 100, 10) / 100; }, toRGB(h, s, v, a) { h = h || this.value.h; s = s || this.value.s; v = v || this.value.b; a = a || this.value.a; let r; let g; let b; let i; let f; let p; let q; let t; if (h && s === undefined && v === undefined) { s = h.s, v = h.v, h = h.h; } i = Math.floor(h * 6); f = h * 6 - i; p = v * (1 - s); q = v * (1 - f * s); t = v * (1 - (1 - f) * s); switch (i % 6) { case 0: r = v, g = t, b = p; break; case 1: r = q, g = v, b = p; break; case 2: r = p, g = v, b = t; break; case 3: r = p, g = q, b = v; break; case 4: r = t, g = p, b = v; break; case 5: r = v, g = p, b = q; break; } return { r: Math.floor(r * 255), g: Math.floor(g * 255), b: Math.floor(b * 255), a, }; }, toHex(h, s, b, a) { const rgb = this.toRGB(h, s, b, a); return `#${((1 << 24) | (parseInt(rgb.r) << 16) | (parseInt(rgb.g) << 8) | parseInt(rgb.b)).toString(16).substr(1)}`; }, toHSL(h, s, b, a) { h = h || this.value.h; s = s || this.value.s; b = b || this.value.b; a = a || this.value.a; const H = h; let L = (2 - s) * b; let S = s * b; if (L > 0 && L <= 1) { S /= L; } else { S /= 2 - L; } L /= 2; if (S > 1) { S = 1; } return { h: isNaN(H) ? 0 : H, s: isNaN(S) ? 0 : S, l: isNaN(L) ? 0 : L, a: isNaN(a) ? 0 : a, }; }, RGBtoHSB(r, g, b, a) { r /= 255; g /= 255; b /= 255; let H; let S; let V; let C; V = Math.max(r, g, b); C = V - Math.min(r, g, b); H = (C === 0 ? null : V === r ? (g - b) / C : V === g ? (b - r) / C + 2 : (r - g) / C + 4 ); H = ((H + 360) % 6) * 60 / 360; S = C === 0 ? 0 : C / V; return { h: this._sanitizeNumber(H), s: S, b: V, a: this._sanitizeNumber(a), }; }, HueToRGB(p, q, h) { if (h < 0) { h += 1; } else if (h > 1) { h -= 1; } if ((h * 6) < 1) { return p + (q - p) * h * 6; } if ((h * 2) < 1) { return q; } if ((h * 3) < 2) { return p + (q - p) * ((2 / 3) - h) * 6; } return p; }, HSLtoRGB(h, s, l, a) { if (s < 0) { s = 0; } let q; if (l <= 0.5) { q = l * (1 + s); } else { q = l + s - (l * s); } const p = 2 * l - q; const tr = h + (1 / 3); const tg = h; const tb = h - (1 / 3); const r = Math.round(this.HueToRGB(p, q, tr) * 255); const g = Math.round(this.HueToRGB(p, q, tg) * 255); const b = Math.round(this.HueToRGB(p, q, tb) * 255); return [r, g, b, this._sanitizeNumber(a)]; }, toString(format) { format = format || 'rgba'; switch (format) { case 'rgb': { var rgb = this.toRGB(); return `rgb(${rgb.r},${rgb.g},${rgb.b})`; } break; case 'rgba': { var rgb = this.toRGB(); return `rgba(${rgb.r},${rgb.g},${rgb.b},${rgb.a})`; } break; case 'hsl': { var hsl = this.toHSL(); return `hsl(${Math.round(hsl.h * 360)},${Math.round(hsl.s * 100)}%,${Math.round(hsl.l * 100)}%)`; } break; case 'hsla': { var hsl = this.toHSL(); return `hsla(${Math.round(hsl.h * 360)},${Math.round(hsl.s * 100)}%,${Math.round(hsl.l * 100)}%,${hsl.a})`; } break; case 'hex': { return this.toHex(); } break; default: { return false; } break; } }, // a set of RE's that can match strings and generate color tuples. // from John Resig color plugin // https://github.com/jquery/jquery-color/ stringParsers: [{ re: /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/, format: 'hex', parse(execResult) { return [ parseInt(execResult[1], 16), parseInt(execResult[2], 16), parseInt(execResult[3], 16), 1, ]; }, }, { re: /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/, format: 'hex', parse(execResult) { return [ parseInt(execResult[1] + execResult[1], 16), parseInt(execResult[2] + execResult[2], 16), parseInt(execResult[3] + execResult[3], 16), 1, ]; }, }, { re: /rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*?\)/, format: 'rgb', parse(execResult) { return [ execResult[1], execResult[2], execResult[3], 1, ]; }, }, { re: /rgb\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*?\)/, format: 'rgb', parse(execResult) { return [ 2.55 * execResult[1], 2.55 * execResult[2], 2.55 * execResult[3], 1, ]; }, }, { re: /rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, format: 'rgba', parse(execResult) { return [ execResult[1], execResult[2], execResult[3], execResult[4], ]; }, }, { re: /rgba\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, format: 'rgba', parse(execResult) { return [ 2.55 * execResult[1], 2.55 * execResult[2], 2.55 * execResult[3], execResult[4], ]; }, }, { re: /hsl\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*?\)/, format: 'hsl', parse(execResult) { return [ execResult[1] / 360, execResult[2] / 100, execResult[3] / 100, execResult[4], ]; }, }, { re: /hsla\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, format: 'hsla', parse(execResult) { return [ execResult[1] / 360, execResult[2] / 100, execResult[3] / 100, execResult[4], ]; }, }, { // predefined color name re: /^([a-z]{3,})$/, format: 'alias', parse(execResult) { const hexval = this.colorNameToHex(execResult[0]) || '#000000'; const match = this.stringParsers[0].re.exec(hexval); const values = match && this.stringParsers[0].parse.apply(this, [match]); return values; }, }], colorNameToHex(name) { // 140 predefined colors from the HTML Colors spec const colors = { aliceblue: '#f0f8ff', antiquewhite: '#faebd7', aqua: '#00ffff', aquamarine: '#7fffd4', azure: '#f0ffff', beige: '#f5f5dc', bisque: '#ffe4c4', black: '#000000', blanchedalmond: '#ffebcd', blue: '#0000ff', blueviolet: '#8a2be2', brown: '#a52a2a', burlywood: '#deb887', cadetblue: '#5f9ea0', chartreuse: '#7fff00', chocolate: '#d2691e', coral: '#ff7f50', cornflowerblue: '#6495ed', cornsilk: '#fff8dc', crimson: '#dc143c', cyan: '#00ffff', darkblue: '#00008b', darkcyan: '#008b8b', darkgoldenrod: '#b8860b', darkgray: '#a9a9a9', darkgreen: '#006400', darkkhaki: '#bdb76b', darkmagenta: '#8b008b', darkolivegreen: '#556b2f', darkorange: '#ff8c00', darkorchid: '#9932cc', darkred: '#8b0000', darksalmon: '#e9967a', darkseagreen: '#8fbc8f', darkslateblue: '#483d8b', darkslategray: '#2f4f4f', darkturquoise: '#00ced1', darkviolet: '#9400d3', deeppink: '#ff1493', deepskyblue: '#00bfff', dimgray: '#696969', dodgerblue: '#1e90ff', firebrick: '#b22222', floralwhite: '#fffaf0', forestgreen: '#228b22', fuchsia: '#ff00ff', gainsboro: '#dcdcdc', ghostwhite: '#f8f8ff', gold: '#ffd700', goldenrod: '#daa520', gray: '#808080', green: '#008000', greenyellow: '#adff2f', honeydew: '#f0fff0', hotpink: '#ff69b4', 'indianred ': '#cd5c5c', 'indigo ': '#4b0082', ivory: '#fffff0', khaki: '#f0e68c', lavender: '#e6e6fa', lavenderblush: '#fff0f5', lawngreen: '#7cfc00', lemonchiffon: '#fffacd', lightblue: '#add8e6', lightcoral: '#f08080', lightcyan: '#e0ffff', lightgoldenrodyellow: '#fafad2', lightgrey: '#d3d3d3', lightgreen: '#90ee90', lightpink: '#ffb6c1', lightsalmon: '#ffa07a', lightseagreen: '#20b2aa', lightskyblue: '#87cefa', lightslategray: '#778899', lightsteelblue: '#b0c4de', lightyellow: '#ffffe0', lime: '#00ff00', limegreen: '#32cd32', linen: '#faf0e6', magenta: '#ff00ff', maroon: '#800000', mediumaquamarine: '#66cdaa', mediumblue: '#0000cd', mediumorchid: '#ba55d3', mediumpurple: '#9370d8', mediumseagreen: '#3cb371', mediumslateblue: '#7b68ee', mediumspringgreen: '#00fa9a', mediumturquoise: '#48d1cc', mediumvioletred: '#c71585', midnightblue: '#191970', mintcream: '#f5fffa', mistyrose: '#ffe4e1', moccasin: '#ffe4b5', navajowhite: '#ffdead', navy: '#000080', oldlace: '#fdf5e6', olive: '#808000', olivedrab: '#6b8e23', orange: '#ffa500', orangered: '#ff4500', orchid: '#da70d6', palegoldenrod: '#eee8aa', palegreen: '#98fb98', paleturquoise: '#afeeee', palevioletred: '#d87093', papayawhip: '#ffefd5', peachpuff: '#ffdab9', peru: '#cd853f', pink: '#ffc0cb', plum: '#dda0dd', powderblue: '#b0e0e6', purple: '#800080', red: '#ff0000', rosybrown: '#bc8f8f', royalblue: '#4169e1', saddlebrown: '#8b4513', salmon: '#fa8072', sandybrown: '#f4a460', seagreen: '#2e8b57', seashell: '#fff5ee', sienna: '#a0522d', silver: '#c0c0c0', skyblue: '#87ceeb', slateblue: '#6a5acd', slategray: '#708090', snow: '#fffafa', springgreen: '#00ff7f', steelblue: '#4682b4', tan: '#d2b48c', teal: '#008080', thistle: '#d8bfd8', tomato: '#ff6347', turquoise: '#40e0d0', violet: '#ee82ee', wheat: '#f5deb3', white: '#ffffff', whitesmoke: '#f5f5f5', yellow: '#ffff00', yellowgreen: '#9acd32', }; if (typeof colors[name.toLowerCase()] !== 'undefined') { return colors[name.toLowerCase()]; } return false; }, }; const defaults = { horizontal: false, // horizontal mode layout ? inline: false, // forces to show the colorpicker as an inline element color: false, // forces a color format: false, // forces a format input: 'input', // children input selector container: false, // container selector component: '.add-on, .input-group-addon', // children component selector sliders: { saturation: { maxLeft: 100, maxTop: 100, callLeft: 'setSaturation', callTop: 'setBrightness', }, hue: { maxLeft: 0, maxTop: 100, callLeft: false, callTop: 'setHue', }, alpha: { maxLeft: 0, maxTop: 100, callLeft: false, callTop: 'setAlpha', }, }, slidersHorz: { saturation: { maxLeft: 100, maxTop: 100, callLeft: 'setSaturation', callTop: 'setBrightness', }, hue: { maxLeft: 100, maxTop: 0, callLeft: 'setHue', callTop: false, }, alpha: { maxLeft: 100, maxTop: 0, callLeft: 'setAlpha', callTop: false, }, }, template: '
', }; const Colorpicker = function (element, options) { this.element = $(element).addClass('colorpicker-element'); this.options = $.extend({}, defaults, this.element.data(), options); this.component = this.options.component; this.component = (this.component !== false) ? this.element.find(this.component) : false; if (this.component && (this.component.length === 0)) { this.component = false; } this.container = (this.options.container === true) ? this.element : this.options.container; this.container = (this.container !== false) ? $(this.container) : false; // Is the element an input? Should we search inside for any input? this.input = this.element.is('input') ? this.element : (this.options.input ? this.element.find(this.options.input) : false); if (this.input && (this.input.length === 0)) { this.input = false; } // Set HSB color this.color = new Color(this.options.color !== false ? this.options.color : this.getValue()); this.format = this.options.format !== false ? this.options.format : this.color.origFormat; // Setup picker this.picker = $(this.options.template); if (this.options.inline) { this.picker.addClass('colorpicker-inline colorpicker-visible'); } else { this.picker.addClass('colorpicker-hidden'); } if (this.options.horizontal) { this.picker.addClass('colorpicker-horizontal'); } if (this.format === 'rgba' || this.format === 'hsla') { this.picker.addClass('colorpicker-with-alpha'); } this.picker.on('mousedown.colorpicker', $.proxy(this.mousedown, this)); this.picker.appendTo(this.container ? this.container : $('body')); // Bind events if (this.input !== false) { this.input.on({ 'keyup.colorpicker': $.proxy(this.keyup, this), }); if (this.component === false) { this.element.on({ 'focus.colorpicker': $.proxy(this.show, this), }); } if (this.options.inline === false) { this.element.on({ 'focusout.colorpicker': $.proxy(this.hide, this), }); } } if (this.component !== false) { this.component.on({ 'click.colorpicker': $.proxy(this.show, this), }); } if ((this.input === false) && (this.component === false)) { this.element.on({ 'click.colorpicker': $.proxy(this.show, this), }); } this.update(); $($.proxy(function () { this.element.trigger('create'); }, this)); }; Colorpicker.version = '2.0.0-beta'; Colorpicker.Color = Color; Colorpicker.prototype = { constructor: Colorpicker, destroy() { this.picker.remove(); this.element.removeData('colorpicker').off('.colorpicker'); if (this.input !== false) { this.input.off('.colorpicker'); } if (this.component !== false) { this.component.off('.colorpicker'); } this.element.removeClass('colorpicker-element'); this.element.trigger({ type: 'destroy', }); }, reposition() { if (this.options.inline !== false) { return false; } const offset = this.component ? this.component.offset() : this.element.offset(); this.picker.css({ top: offset.top + (this.component ? this.component.outerHeight() : this.element.outerHeight()), left: offset.left, }); }, show(e) { if (this.isDisabled()) { return false; } this.picker.addClass('colorpicker-visible').removeClass('colorpicker-hidden'); this.reposition(); $(window).on('resize.colorpicker', $.proxy(this.reposition, this)); if (!this.hasInput() && e) { if (e.stopPropagation && e.preventDefault) { e.stopPropagation(); e.preventDefault(); } } if (this.options.inline === false) { $(window.document).on({ 'mousedown.colorpicker': $.proxy(this.hide, this), }); } this.element.trigger({ type: 'showPicker', color: this.color, }); }, hide() { this.picker.addClass('colorpicker-hidden').removeClass('colorpicker-visible'); $(window).off('resize.colorpicker', this.reposition); $(document).off({ 'mousedown.colorpicker': this.hide, }); this.update(); this.element.trigger({ type: 'hidePicker', color: this.color, }); }, updateData(val) { val = val || this.color.toString(this.format); this.element.data('color', val); return val; }, updateInput(val) { val = val || this.color.toString(this.format); if (this.input !== false) { this.input.prop('value', val); } return val; }, updatePicker(val) { if (val !== undefined) { this.color = new Color(val); } let sl = (this.options.horizontal === false) ? this.options.sliders : this.options.slidersHorz; const icns = this.picker.find('i'); if (icns.length === 0) { return; } if (this.options.horizontal === false) { sl = this.options.sliders; icns.eq(1).css('top', sl.hue.maxTop * (1 - this.color.value.h)).end() .eq(2) .css('top', sl.alpha.maxTop * (1 - this.color.value.a)); } else { sl = this.options.slidersHorz; icns.eq(1).css('left', sl.hue.maxLeft * (1 - this.color.value.h)).end() .eq(2) .css('left', sl.alpha.maxLeft * (1 - this.color.value.a)); } icns.eq(0).css({ top: sl.saturation.maxTop - this.color.value.b * sl.saturation.maxTop, left: this.color.value.s * sl.saturation.maxLeft, }); this.picker.find('.colorpicker-saturation').css('backgroundColor', this.color.toHex(this.color.value.h, 1, 1, 1)); this.picker.find('.colorpicker-alpha').css('backgroundColor', this.color.toHex()); this.picker.find('.colorpicker-color, .colorpicker-color div').css('backgroundColor', this.color.toString(this.format)); return val; }, updateComponent(val) { val = val || this.color.toString(this.format); if (this.component !== false) { const icn = this.component.find('i').eq(0); if (icn.length > 0) { icn.css({ backgroundColor: val, }); } else { this.component.css({ backgroundColor: val, }); } } return val; }, update(force) { const val = this.updateComponent(); if ((this.getValue(false) !== false) || (force === true)) { // Update input/data only if the current value is not blank this.updateInput(val); this.updateData(val); } this.updatePicker(); return val; }, setValue(val) { // set color manually this.color = new Color(val); this.update(); this.element.trigger({ type: 'changeColor', color: this.color, value: val, }); }, getValue(defaultValue) { defaultValue = (defaultValue === undefined) ? '#000000' : defaultValue; let val; if (this.hasInput()) { val = this.input.val(); } else { val = this.element.data('color'); } if ((val === undefined) || (val === '') || (val === null)) { // if not defined or empty, return default val = defaultValue; } return val; }, hasInput() { return (this.input !== false); }, isDisabled() { if (this.hasInput()) { return (this.input.prop('disabled') === true); } return false; }, disable() { if (this.hasInput()) { this.input.prop('disabled', true); return true; } return false; }, enable() { if (this.hasInput()) { this.input.prop('disabled', false); return true; } return false; }, currentSlider: null, mousePointer: { left: 0, top: 0, }, mousedown(e) { e.stopPropagation(); e.preventDefault(); const target = $(e.target); // detect the slider and set the limits and callbacks const zone = target.closest('div'); const sl = this.options.horizontal ? this.options.slidersHorz : this.options.sliders; if (!zone.is('.colorpicker')) { if (zone.is('.colorpicker-saturation')) { this.currentSlider = $.extend({}, sl.saturation); } else if (zone.is('.colorpicker-hue')) { this.currentSlider = $.extend({}, sl.hue); } else if (zone.is('.colorpicker-alpha')) { this.currentSlider = $.extend({}, sl.alpha); } else { return false; } const offset = zone.offset(); // reference to guide's style this.currentSlider.guide = zone.find('i')[0].style; this.currentSlider.left = e.pageX - offset.left; this.currentSlider.top = e.pageY - offset.top; this.mousePointer = { left: e.pageX, top: e.pageY, }; // trigger mousemove to move the guide to the current position $(document).on({ 'mousemove.colorpicker': $.proxy(this.mousemove, this), 'mouseup.colorpicker': $.proxy(this.mouseup, this), }).trigger('mousemove'); } return false; }, mousemove(e) { e.stopPropagation(); e.preventDefault(); const left = Math.max( 0, Math.min( this.currentSlider.maxLeft, this.currentSlider.left + ((e.pageX || this.mousePointer.left) - this.mousePointer.left), ), ); const top = Math.max( 0, Math.min( this.currentSlider.maxTop, this.currentSlider.top + ((e.pageY || this.mousePointer.top) - this.mousePointer.top), ), ); this.currentSlider.guide.left = `${left}px`; this.currentSlider.guide.top = `${top}px`; if (this.currentSlider.callLeft) { this.color[this.currentSlider.callLeft].call(this.color, left / 100); } if (this.currentSlider.callTop) { this.color[this.currentSlider.callTop].call(this.color, top / 100); } this.update(true); this.element.trigger({ type: 'changeColor', color: this.color, }); return false; }, mouseup(e) { e.stopPropagation(); e.preventDefault(); $(document).off({ 'mousemove.colorpicker': this.mousemove, 'mouseup.colorpicker': this.mouseup, }); return false; }, keyup(e) { if ((e.keyCode === 38)) { if (this.color.value.a < 1) { this.color.value.a = Math.round((this.color.value.a + 0.01) * 100) / 100; } this.update(true); } else if ((e.keyCode === 40)) { if (this.color.value.a > 0) { this.color.value.a = Math.round((this.color.value.a - 0.01) * 100) / 100; } this.update(true); } else { var val = this.input.val(); this.color = new Color(val); if (this.getValue(false) !== false) { this.updateData(); this.updateComponent(); this.updatePicker(); } } this.element.trigger({ type: 'changeColor', color: this.color, value: val, }); }, }; $.colorpicker = Colorpicker; $.fn.colorpicker = function (option) { const pickerArgs = arguments; return this.each(function () { const $this = $(this); const inst = $this.data('colorpicker'); const options = ((typeof option === 'object') ? option : {}); if ((!inst) && (typeof option !== 'string')) { $this.data('colorpicker', new Colorpicker(this, options)); } else if (typeof option === 'string') { inst[option].apply(inst, Array.prototype.slice.call(pickerArgs, 1)); } }); }; $.fn.colorpicker.constructor = Colorpicker; }(window.jQuery));