diff --git a/CookieMonster.js b/CookieMonster.js
index 7609daa..6f582de 100644
--- a/CookieMonster.js
+++ b/CookieMonster.js
@@ -3259,13 +3259,12 @@ CM.Disp.CreatePrefOption = function(config) {
var innerDiv = document.createElement('div');
innerDiv.className = 'listing';
var input = document.createElement('input');
- input.id = CM.ConfigPrefix + 'Color' + CM.Disp.colors[i];
- input.className = 'option';
+ input.id = CM.Disp.colors[i];
input.style.width = '65px';
input.setAttribute('value', CM.Options.Colors[CM.Disp.colors[i]]);
innerDiv.appendChild(input);
- eval('var change = function() {CM.Options.Colors[\'' + CM.Disp.colors[i] + '\'] = l(CM.ConfigPrefix + \'Color\' + \'' + CM.Disp.colors[i] + '\').value; CM.Disp.UpdateColors(); CM.Config.SaveConfig();}');
- var jscolorpicker = new jscolor.color(input, {hash: true, caps: false, pickerZIndex: 1000000, pickerPosition: 'right', onImmediateChange: change});
+ let change = function() {CM.Options.Colors[this.targetElement.id] = this.toHEXString(); CM.Disp.UpdateColors(); CM.Config.SaveConfig();};
+ let picker = new JSColor(input, {hash: true, position: "right", onInput: change})
var label = document.createElement('label');
label.textContent = CM.ConfigData.Colors.desc[CM.Disp.colors[i]];
innerDiv.appendChild(label);
@@ -5507,12 +5506,14 @@ CM.Footer.AddJscolor = function() {
CM.Footer.Jscolor = document.createElement('script');
CM.Footer.Jscolor.type = 'text/javascript';
CM.Footer.Jscolor.setAttribute('src', 'https://aktanusa.github.io/CookieMonster/jscolor/jscolor.js');
- document.head.appendChild(CM.Footer.Jscolor);
+ document.head.appendChild(CM.Footer.Jscolor);
+ jscolor.init();
}
/**
* This functions starts the initizialization and register CookieMonster
* It is called as the last function in this script's execution
+ * TODO: Make this async await
*/
if (!CM.isRunning) {
CM.Footer.AddJscolor();
diff --git a/jscolor/jscolor.js b/jscolor/jscolor.js
index ef3bce5..ea3e985 100644
--- a/jscolor/jscolor.js
+++ b/jscolor/jscolor.js
@@ -1,997 +1,3524 @@
-/**
- * jscolor, JavaScript Color Picker
- *
- * @version 1.4.3
- * @license GNU Lesser General Public License, http://www.gnu.org/copyleft/lesser.html
- * @author Jan Odvarko, http://odvarko.cz
- * @created 2008-06-15
- * @updated 2014-07-16
- * @link http://jscolor.com
- */
-
-
-var jscolor = {
-
-
- dir : '', // location of jscolor directory (leave empty to autodetect)
- bindClass : 'color', // class name
- binding : true, // automatic binding via
- preloading : true, // use image preloading?
-
-
- install : function() {
- jscolor.addEvent(window, 'load', jscolor.init);
- },
-
-
- init : function() {
- if(jscolor.binding) {
- jscolor.bind();
- }
- if(jscolor.preloading) {
- jscolor.preload();
- }
- },
-
-
- getDir : function() {
- if(!jscolor.dir) {
- var detected = jscolor.detectDir();
- jscolor.dir = detected!==false ? detected : 'jscolor/';
- }
- return jscolor.dir;
- },
-
-
- detectDir : function() {
- var base = location.href;
-
- var e = document.getElementsByTagName('base');
- for(var i=0; i vs[a] ?
- (-vp[a]+tp[a]+ts[a]/2 > vs[a]/2 && tp[a]+ts[a]-ps[a] >= 0 ? tp[a]+ts[a]-ps[a] : tp[a]) :
- tp[a],
- -vp[b]+tp[b]+ts[b]+ps[b]-l+l*c > vs[b] ?
- (-vp[b]+tp[b]+ts[b]/2 > vs[b]/2 && tp[b]+ts[b]-l-l*c >= 0 ? tp[b]+ts[b]-l-l*c : tp[b]+ts[b]-l+l*c) :
- (tp[b]+ts[b]-l+l*c >= 0 ? tp[b]+ts[b]-l+l*c : tp[b]+ts[b]-l-l*c)
- ];
- }
- drawPicker(pp[a], pp[b]);
- }
- };
-
-
- this.importColor = function() {
- if(!valueElement) {
- this.exportColor();
- } else {
- if(!this.adjust) {
- if(!this.fromString(valueElement.value, leaveValue)) {
- styleElement.style.backgroundImage = styleElement.jscStyle.backgroundImage;
- styleElement.style.backgroundColor = styleElement.jscStyle.backgroundColor;
- styleElement.style.color = styleElement.jscStyle.color;
- this.exportColor(leaveValue | leaveStyle);
- }
- } else if(!this.required && /^\s*$/.test(valueElement.value)) {
- valueElement.value = '';
- styleElement.style.backgroundImage = styleElement.jscStyle.backgroundImage;
- styleElement.style.backgroundColor = styleElement.jscStyle.backgroundColor;
- styleElement.style.color = styleElement.jscStyle.color;
- this.exportColor(leaveValue | leaveStyle);
-
- } else if(this.fromString(valueElement.value)) {
- // OK
- } else {
- this.exportColor();
- }
- }
- };
-
-
- this.exportColor = function(flags) {
- if(!(flags & leaveValue) && valueElement) {
- var value = this.toString();
- if(this.caps) { value = value.toUpperCase(); }
- if(this.hash) { value = '#'+value; }
- valueElement.value = value;
- }
- if(!(flags & leaveStyle) && styleElement) {
- styleElement.style.backgroundImage = "none";
- styleElement.style.backgroundColor =
- '#'+this.toString();
- styleElement.style.color =
- 0.213 * this.rgb[0] +
- 0.715 * this.rgb[1] +
- 0.072 * this.rgb[2]
- < 0.5 ? '#FFF' : '#000';
- }
- if(!(flags & leavePad) && isPickerOwner()) {
- redrawPad();
- }
- if(!(flags & leaveSld) && isPickerOwner()) {
- redrawSld();
- }
- };
-
-
- this.fromHSV = function(h, s, v, flags) { // null = don't change
- if(h !== null) { h = Math.max(0.0, this.minH, Math.min(6.0, this.maxH, h)); }
- if(s !== null) { s = Math.max(0.0, this.minS, Math.min(1.0, this.maxS, s)); }
- if(v !== null) { v = Math.max(0.0, this.minV, Math.min(1.0, this.maxV, v)); }
-
- this.rgb = HSV_RGB(
- h===null ? this.hsv[0] : (this.hsv[0]=h),
- s===null ? this.hsv[1] : (this.hsv[1]=s),
- v===null ? this.hsv[2] : (this.hsv[2]=v)
- );
-
- this.exportColor(flags);
- };
-
-
- this.fromRGB = function(r, g, b, flags) { // null = don't change
- if(r !== null) { r = Math.max(0.0, Math.min(1.0, r)); }
- if(g !== null) { g = Math.max(0.0, Math.min(1.0, g)); }
- if(b !== null) { b = Math.max(0.0, Math.min(1.0, b)); }
-
- var hsv = RGB_HSV(
- r===null ? this.rgb[0] : r,
- g===null ? this.rgb[1] : g,
- b===null ? this.rgb[2] : b
- );
- if(hsv[0] !== null) {
- this.hsv[0] = Math.max(0.0, this.minH, Math.min(6.0, this.maxH, hsv[0]));
- }
- if(hsv[2] !== 0) {
- this.hsv[1] = hsv[1]===null ? null : Math.max(0.0, this.minS, Math.min(1.0, this.maxS, hsv[1]));
- }
- this.hsv[2] = hsv[2]===null ? null : Math.max(0.0, this.minV, Math.min(1.0, this.maxV, hsv[2]));
-
- // update RGB according to final HSV, as some values might be trimmed
- var rgb = HSV_RGB(this.hsv[0], this.hsv[1], this.hsv[2]);
- this.rgb[0] = rgb[0];
- this.rgb[1] = rgb[1];
- this.rgb[2] = rgb[2];
-
- this.exportColor(flags);
- };
-
-
- this.fromString = function(hex, flags) {
- var m = hex.match(/^\W*([0-9A-F]{3}([0-9A-F]{3})?)\W*$/i);
- if(!m) {
- return false;
- } else {
- if(m[1].length === 6) { // 6-char notation
- this.fromRGB(
- parseInt(m[1].substr(0,2),16) / 255,
- parseInt(m[1].substr(2,2),16) / 255,
- parseInt(m[1].substr(4,2),16) / 255,
- flags
- );
- } else { // 3-char notation
- this.fromRGB(
- parseInt(m[1].charAt(0)+m[1].charAt(0),16) / 255,
- parseInt(m[1].charAt(1)+m[1].charAt(1),16) / 255,
- parseInt(m[1].charAt(2)+m[1].charAt(2),16) / 255,
- flags
- );
- }
- return true;
- }
- };
-
-
- this.toString = function() {
- return (
- (0x100 | Math.round(255*this.rgb[0])).toString(16).substr(1) +
- (0x100 | Math.round(255*this.rgb[1])).toString(16).substr(1) +
- (0x100 | Math.round(255*this.rgb[2])).toString(16).substr(1)
- );
- };
-
-
- function RGB_HSV(r, g, b) {
- var n = Math.min(Math.min(r,g),b);
- var v = Math.max(Math.max(r,g),b);
- var m = v - n;
- if(m === 0) { return [ null, 0, v ]; }
- var h = r===n ? 3+(b-g)/m : (g===n ? 5+(r-b)/m : 1+(g-r)/m);
- return [ h===6?0:h, m/v, v ];
- }
-
-
- function HSV_RGB(h, s, v) {
- if(h === null) { return [ v, v, v ]; }
- var i = Math.floor(h);
- var f = i%2 ? h-i : 1-(h-i);
- var m = v * (1 - s);
- var n = v * (1 - s*f);
- switch(i) {
- case 6:
- case 0: return [v,n,m];
- case 1: return [n,v,m];
- case 2: return [m,v,n];
- case 3: return [m,n,v];
- case 4: return [n,m,v];
- case 5: return [v,m,n];
- }
- }
-
-
- function removePicker() {
- delete jscolor.picker.owner;
- document.getElementsByTagName('body')[0].removeChild(jscolor.picker.boxB);
- }
-
-
- function drawPicker(x, y) {
- if(!jscolor.picker) {
- jscolor.picker = {
- box : document.createElement('div'),
- boxB : document.createElement('div'),
- pad : document.createElement('div'),
- padB : document.createElement('div'),
- padM : document.createElement('div'),
- sld : document.createElement('div'),
- sldB : document.createElement('div'),
- sldM : document.createElement('div'),
- btn : document.createElement('div'),
- btnS : document.createElement('span'),
- btnT : document.createTextNode(THIS.pickerCloseText)
- };
- for(var i=0,segSize=4; i try to evaluate the options string as JavaScript object
+ try {
+ opts = (new Function ('var opts = (' + str + '); return typeof opts === "object" ? opts : {};'))();
+ } catch (eEval) {
+ throw new Error('Could not evaluate jscolor options: ' + eEval);
+ }
+ }
+ }
+ return opts;
+ },
+
+
+ getInstances : function () {
+ var inst = [];
+ for (var i = 0; i < jsc.instances.length; i += 1) {
+ // if the targetElement still exists, the instance is considered "alive"
+ if (jsc.instances[i] && jsc.instances[i].targetElement) {
+ inst.push(jsc.instances[i]);
+ }
+ }
+ return inst;
+ },
+
+
+ createEl : function (tagName) {
+ var el = window.document.createElement(tagName);
+ jsc.setData(el, 'gui', true);
+ return el;
+ },
+
+
+ node : function (nodeOrSelector) {
+ if (!nodeOrSelector) {
+ return null;
+ }
+
+ if (typeof nodeOrSelector === 'string') {
+ // query selector
+ var sel = nodeOrSelector;
+ var el = null;
+ try {
+ el = window.document.querySelector(sel);
+ } catch (e) {
+ console.warn(e);
+ return null;
+ }
+ if (!el) {
+ console.warn('No element matches the selector: %s', sel);
+ }
+ return el;
+ }
+
+ if (jsc.isNode(nodeOrSelector)) {
+ // DOM node
+ return nodeOrSelector;
+ }
+
+ console.warn('Invalid node of type %s: %s', typeof nodeOrSelector, nodeOrSelector);
+ return null;
+ },
+
+
+ // See https://stackoverflow.com/questions/384286/
+ isNode : function (val) {
+ if (typeof Node === 'object') {
+ return val instanceof Node;
+ }
+ return val && typeof val === 'object' && typeof val.nodeType === 'number' && typeof val.nodeName === 'string';
+ },
+
+
+ nodeName : function (node) {
+ if (node && node.nodeName) {
+ return node.nodeName.toLowerCase();
+ }
+ return false;
+ },
+
+
+ removeChildren : function (node) {
+ while (node.firstChild) {
+ node.removeChild(node.firstChild);
+ }
+ },
+
+
+ isTextInput : function (el) {
+ return el && jsc.nodeName(el) === 'input' && el.type.toLowerCase() === 'text';
+ },
+
+
+ isButton : function (el) {
+ if (!el) {
+ return false;
+ }
+ var n = jsc.nodeName(el);
+ return (
+ (n === 'button') ||
+ (n === 'input' && ['button', 'submit', 'reset'].indexOf(el.type.toLowerCase()) > -1)
+ );
+ },
+
+
+ isButtonEmpty : function (el) {
+ switch (jsc.nodeName(el)) {
+ case 'input': return (!el.value || el.value.trim() === '');
+ case 'button': return (el.textContent.trim() === '');
+ }
+ return null; // could not determine element's text
+ },
+
+
+ // See https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
+ isPassiveEventSupported : (function () {
+ var supported = false;
+
+ try {
+ var opts = Object.defineProperty({}, 'passive', {
+ get: function () { supported = true; }
+ });
+ window.addEventListener('testPassive', null, opts);
+ window.removeEventListener('testPassive', null, opts);
+ } catch (e) {}
+
+ return supported;
+ })(),
+
+
+ isColorAttrSupported : (function () {
+ var elm = window.document.createElement('input');
+ if (elm.setAttribute) {
+ elm.setAttribute('type', 'color');
+ if (elm.type.toLowerCase() == 'color') {
+ return true;
+ }
+ }
+ return false;
+ })(),
+
+
+ dataProp : '_data_jscolor',
+
+
+ // usage:
+ // setData(obj, prop, value)
+ // setData(obj, {prop:value, ...})
+ //
+ setData : function () {
+ var obj = arguments[0];
+
+ if (arguments.length === 3) {
+ // setting a single property
+ var data = obj.hasOwnProperty(jsc.dataProp) ? obj[jsc.dataProp] : (obj[jsc.dataProp] = {});
+ var prop = arguments[1];
+ var value = arguments[2];
+
+ data[prop] = value;
+ return true;
+
+ } else if (arguments.length === 2 && typeof arguments[1] === 'object') {
+ // setting multiple properties
+ var data = obj.hasOwnProperty(jsc.dataProp) ? obj[jsc.dataProp] : (obj[jsc.dataProp] = {});
+ var map = arguments[1];
+
+ for (var prop in map) {
+ if (map.hasOwnProperty(prop)) {
+ data[prop] = map[prop];
+ }
+ }
+ return true;
+ }
+
+ throw new Error('Invalid arguments');
+ },
+
+
+ // usage:
+ // removeData(obj, prop, [prop...])
+ //
+ removeData : function () {
+ var obj = arguments[0];
+ if (!obj.hasOwnProperty(jsc.dataProp)) {
+ return true; // data object does not exist
+ }
+ for (var i = 1; i < arguments.length; i += 1) {
+ var prop = arguments[i];
+ delete obj[jsc.dataProp][prop];
+ }
+ return true;
+ },
+
+
+ getData : function (obj, prop, setDefault) {
+ if (!obj.hasOwnProperty(jsc.dataProp)) {
+ // data object does not exist
+ if (setDefault !== undefined) {
+ obj[jsc.dataProp] = {}; // create data object
+ } else {
+ return undefined; // no value to return
+ }
+ }
+ var data = obj[jsc.dataProp];
+
+ if (!data.hasOwnProperty(prop) && setDefault !== undefined) {
+ data[prop] = setDefault;
+ }
+ return data[prop];
+ },
+
+
+ getDataAttr : function (el, name) {
+ var attrName = 'data-' + name;
+ var attrValue = el.getAttribute(attrName);
+ return attrValue;
+ },
+
+
+ setDataAttr : function (el, name, value) {
+ var attrName = 'data-' + name;
+ el.setAttribute(attrName, value);
+ },
+
+
+ _attachedGroupEvents : {},
+
+
+ attachGroupEvent : function (groupName, el, evnt, func) {
+ if (!jsc._attachedGroupEvents.hasOwnProperty(groupName)) {
+ jsc._attachedGroupEvents[groupName] = [];
+ }
+ jsc._attachedGroupEvents[groupName].push([el, evnt, func]);
+ el.addEventListener(evnt, func, false);
+ },
+
+
+ detachGroupEvents : function (groupName) {
+ if (jsc._attachedGroupEvents.hasOwnProperty(groupName)) {
+ for (var i = 0; i < jsc._attachedGroupEvents[groupName].length; i += 1) {
+ var evt = jsc._attachedGroupEvents[groupName][i];
+ evt[0].removeEventListener(evt[1], evt[2], false);
+ }
+ delete jsc._attachedGroupEvents[groupName];
+ }
+ },
+
+
+ preventDefault : function (e) {
+ if (e.preventDefault) { e.preventDefault(); }
+ e.returnValue = false;
+ },
+
+
+ captureTarget : function (target) {
+ // IE
+ if (target.setCapture) {
+ jsc._capturedTarget = target;
+ jsc._capturedTarget.setCapture();
+ }
+ },
+
+
+ releaseTarget : function () {
+ // IE
+ if (jsc._capturedTarget) {
+ jsc._capturedTarget.releaseCapture();
+ jsc._capturedTarget = null;
+ }
+ },
+
+
+ triggerEvent : function (el, eventName, bubbles, cancelable) {
+ if (!el) {
+ return;
+ }
+
+ var ev = null;
+
+ if (typeof Event === 'function') {
+ ev = new Event(eventName, {
+ bubbles: bubbles,
+ cancelable: cancelable
+ });
+ } else {
+ // IE
+ ev = window.document.createEvent('Event');
+ ev.initEvent(eventName, bubbles, cancelable);
+ }
+
+ if (!ev) {
+ return false;
+ }
+
+ // so that we know that the event was triggered internally
+ jsc.setData(ev, 'internal', true);
+
+ el.dispatchEvent(ev);
+ return true;
+ },
+
+
+ triggerInputEvent : function (el, eventName, bubbles, cancelable) {
+ if (!el) {
+ return;
+ }
+ if (jsc.isTextInput(el)) {
+ jsc.triggerEvent(el, eventName, bubbles, cancelable);
+ }
+ },
+
+
+ eventKey : function (ev) {
+ var keys = {
+ 9: 'Tab',
+ 13: 'Enter',
+ 27: 'Escape',
+ };
+ if (typeof ev.code === 'string') {
+ return ev.code;
+ } else if (ev.keyCode !== undefined && keys.hasOwnProperty(ev.keyCode)) {
+ return keys[ev.keyCode];
+ }
+ return null;
+ },
+
+
+ strList : function (str) {
+ if (!str) {
+ return [];
+ }
+ return str.replace(/^\s+|\s+$/g, '').split(/\s+/);
+ },
+
+
+ // The className parameter (str) can only contain a single class name
+ hasClass : function (elm, className) {
+ if (!className) {
+ return false;
+ }
+ if (elm.classList !== undefined) {
+ return elm.classList.contains(className);
+ }
+ // polyfill
+ return -1 != (' ' + elm.className.replace(/\s+/g, ' ') + ' ').indexOf(' ' + className + ' ');
+ },
+
+
+ // The className parameter (str) can contain multiple class names separated by whitespace
+ addClass : function (elm, className) {
+ var classNames = jsc.strList(className);
+
+ if (elm.classList !== undefined) {
+ for (var i = 0; i < classNames.length; i += 1) {
+ elm.classList.add(classNames[i]);
+ }
+ return;
+ }
+ // polyfill
+ for (var i = 0; i < classNames.length; i += 1) {
+ if (!jsc.hasClass(elm, classNames[i])) {
+ elm.className += (elm.className ? ' ' : '') + classNames[i];
+ }
+ }
+ },
+
+
+ // The className parameter (str) can contain multiple class names separated by whitespace
+ removeClass : function (elm, className) {
+ var classNames = jsc.strList(className);
+
+ if (elm.classList !== undefined) {
+ for (var i = 0; i < classNames.length; i += 1) {
+ elm.classList.remove(classNames[i]);
+ }
+ return;
+ }
+ // polyfill
+ for (var i = 0; i < classNames.length; i += 1) {
+ var repl = new RegExp(
+ '^\\s*' + classNames[i] + '\\s*|' +
+ '\\s*' + classNames[i] + '\\s*$|' +
+ '\\s+' + classNames[i] + '(\\s+)',
+ 'g'
+ );
+ elm.className = elm.className.replace(repl, '$1');
+ }
+ },
+
+
+ getCompStyle : function (elm) {
+ var compStyle = window.getComputedStyle ? window.getComputedStyle(elm) : elm.currentStyle;
+
+ // Note: In Firefox, getComputedStyle returns null in a hidden iframe,
+ // that's why we need to check if the returned value is non-empty
+ if (!compStyle) {
+ return {};
+ }
+ return compStyle;
+ },
+
+
+ // Note:
+ // Setting a property to NULL reverts it to the state before it was first set
+ // with the 'reversible' flag enabled
+ //
+ setStyle : function (elm, styles, important, reversible) {
+ // using '' for standard priority (IE10 apparently doesn't like value undefined)
+ var priority = important ? 'important' : '';
+ var origStyle = null;
+
+ for (var prop in styles) {
+ if (styles.hasOwnProperty(prop)) {
+ var setVal = null;
+
+ if (styles[prop] === null) {
+ // reverting a property value
+
+ if (!origStyle) {
+ // get the original style object, but dont't try to create it if it doesn't exist
+ origStyle = jsc.getData(elm, 'origStyle');
+ }
+ if (origStyle && origStyle.hasOwnProperty(prop)) {
+ // we have property's original value -> use it
+ setVal = origStyle[prop];
+ }
+
+ } else {
+ // setting a property value
+
+ if (reversible) {
+ if (!origStyle) {
+ // get the original style object and if it doesn't exist, create it
+ origStyle = jsc.getData(elm, 'origStyle', {});
+ }
+ if (!origStyle.hasOwnProperty(prop)) {
+ // original property value not yet stored -> store it
+ origStyle[prop] = elm.style[prop];
+ }
+ }
+ setVal = styles[prop];
+ }
+
+ if (setVal !== null) {
+ elm.style.setProperty(prop, setVal, priority);
+ }
+ }
+ }
+ },
+
+
+ hexColor : function (r, g, b) {
+ return '#' + (
+ ('0' + Math.round(r).toString(16)).substr(-2) +
+ ('0' + Math.round(g).toString(16)).substr(-2) +
+ ('0' + Math.round(b).toString(16)).substr(-2)
+ ).toUpperCase();
+ },
+
+
+ hexaColor : function (r, g, b, a) {
+ return '#' + (
+ ('0' + Math.round(r).toString(16)).substr(-2) +
+ ('0' + Math.round(g).toString(16)).substr(-2) +
+ ('0' + Math.round(b).toString(16)).substr(-2) +
+ ('0' + Math.round(a * 255).toString(16)).substr(-2)
+ ).toUpperCase();
+ },
+
+
+ rgbColor : function (r, g, b) {
+ return 'rgb(' +
+ Math.round(r) + ',' +
+ Math.round(g) + ',' +
+ Math.round(b) +
+ ')';
+ },
+
+
+ rgbaColor : function (r, g, b, a) {
+ return 'rgba(' +
+ Math.round(r) + ',' +
+ Math.round(g) + ',' +
+ Math.round(b) + ',' +
+ (Math.round((a===undefined || a===null ? 1 : a) * 100) / 100) +
+ ')';
+ },
+
+
+ linearGradient : (function () {
+
+ function getFuncName () {
+ var stdName = 'linear-gradient';
+ var prefixes = ['', '-webkit-', '-moz-', '-o-', '-ms-'];
+ var helper = window.document.createElement('div');
+
+ for (var i = 0; i < prefixes.length; i += 1) {
+ var tryFunc = prefixes[i] + stdName;
+ var tryVal = tryFunc + '(to right, rgba(0,0,0,0), rgba(0,0,0,0))';
+
+ helper.style.background = tryVal;
+ if (helper.style.background) { // CSS background successfully set -> function name is supported
+ return tryFunc;
+ }
+ }
+ return stdName; // fallback to standard 'linear-gradient' without vendor prefix
+ }
+
+ var funcName = getFuncName();
+
+ return function () {
+ return funcName + '(' + Array.prototype.join.call(arguments, ', ') + ')';
+ };
+
+ })(),
+
+
+ setBorderRadius : function (elm, value) {
+ jsc.setStyle(elm, {'border-radius' : value || '0'});
+ },
+
+
+ setBoxShadow : function (elm, value) {
+ jsc.setStyle(elm, {'box-shadow': value || 'none'});
+ },
+
+
+ getElementPos : function (e, relativeToViewport) {
+ var x=0, y=0;
+ var rect = e.getBoundingClientRect();
+ x = rect.left;
+ y = rect.top;
+ if (!relativeToViewport) {
+ var viewPos = jsc.getViewPos();
+ x += viewPos[0];
+ y += viewPos[1];
+ }
+ return [x, y];
+ },
+
+
+ getElementSize : function (e) {
+ return [e.offsetWidth, e.offsetHeight];
+ },
+
+
+ // get pointer's X/Y coordinates relative to viewport
+ getAbsPointerPos : function (e) {
+ var x = 0, y = 0;
+ if (typeof e.changedTouches !== 'undefined' && e.changedTouches.length) {
+ // touch devices
+ x = e.changedTouches[0].clientX;
+ y = e.changedTouches[0].clientY;
+ } else if (typeof e.clientX === 'number') {
+ x = e.clientX;
+ y = e.clientY;
+ }
+ return { x: x, y: y };
+ },
+
+
+ // get pointer's X/Y coordinates relative to target element
+ getRelPointerPos : function (e) {
+ var target = e.target || e.srcElement;
+ var targetRect = target.getBoundingClientRect();
+
+ var x = 0, y = 0;
+
+ var clientX = 0, clientY = 0;
+ if (typeof e.changedTouches !== 'undefined' && e.changedTouches.length) {
+ // touch devices
+ clientX = e.changedTouches[0].clientX;
+ clientY = e.changedTouches[0].clientY;
+ } else if (typeof e.clientX === 'number') {
+ clientX = e.clientX;
+ clientY = e.clientY;
+ }
+
+ x = clientX - targetRect.left;
+ y = clientY - targetRect.top;
+ return { x: x, y: y };
+ },
+
+
+ getViewPos : function () {
+ var doc = window.document.documentElement;
+ return [
+ (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0),
+ (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0)
+ ];
+ },
+
+
+ getViewSize : function () {
+ var doc = window.document.documentElement;
+ return [
+ (window.innerWidth || doc.clientWidth),
+ (window.innerHeight || doc.clientHeight),
+ ];
+ },
+
+
+ // r: 0-255
+ // g: 0-255
+ // b: 0-255
+ //
+ // returns: [ 0-360, 0-100, 0-100 ]
+ //
+ RGB_HSV : function (r, g, b) {
+ r /= 255;
+ g /= 255;
+ b /= 255;
+ var n = Math.min(Math.min(r,g),b);
+ var v = Math.max(Math.max(r,g),b);
+ var m = v - n;
+ if (m === 0) { return [ null, 0, 100 * v ]; }
+ var h = r===n ? 3+(b-g)/m : (g===n ? 5+(r-b)/m : 1+(g-r)/m);
+ return [
+ 60 * (h===6?0:h),
+ 100 * (m/v),
+ 100 * v
+ ];
+ },
+
+
+ // h: 0-360
+ // s: 0-100
+ // v: 0-100
+ //
+ // returns: [ 0-255, 0-255, 0-255 ]
+ //
+ HSV_RGB : function (h, s, v) {
+ var u = 255 * (v / 100);
+
+ if (h === null) {
+ return [ u, u, u ];
+ }
+
+ h /= 60;
+ s /= 100;
+
+ var i = Math.floor(h);
+ var f = i%2 ? h-i : 1-(h-i);
+ var m = u * (1 - s);
+ var n = u * (1 - s * f);
+ switch (i) {
+ case 6:
+ case 0: return [u,n,m];
+ case 1: return [n,u,m];
+ case 2: return [m,u,n];
+ case 3: return [m,n,u];
+ case 4: return [n,m,u];
+ case 5: return [u,m,n];
+ }
+ },
+
+
+ parseColorString : function (str) {
+ var ret = {
+ rgba: null,
+ format: null // 'hex' | 'hexa' | 'rgb' | 'rgba'
+ };
+
+ var m;
+
+ if (m = str.match(/^\W*([0-9A-F]{3,8})\W*$/i)) {
+ // HEX notation
+
+ if (m[1].length === 8) {
+ // 8-char notation (= with alpha)
+ ret.format = 'hexa';
+ ret.rgba = [
+ parseInt(m[1].substr(0,2),16),
+ parseInt(m[1].substr(2,2),16),
+ parseInt(m[1].substr(4,2),16),
+ parseInt(m[1].substr(6,2),16) / 255
+ ];
+
+ } else if (m[1].length === 6) {
+ // 6-char notation
+ ret.format = 'hex';
+ ret.rgba = [
+ parseInt(m[1].substr(0,2),16),
+ parseInt(m[1].substr(2,2),16),
+ parseInt(m[1].substr(4,2),16),
+ null
+ ];
+
+ } else if (m[1].length === 3) {
+ // 3-char notation
+ ret.format = 'hex';
+ ret.rgba = [
+ parseInt(m[1].charAt(0) + m[1].charAt(0),16),
+ parseInt(m[1].charAt(1) + m[1].charAt(1),16),
+ parseInt(m[1].charAt(2) + m[1].charAt(2),16),
+ null
+ ];
+
+ } else {
+ return false;
+ }
+
+ return ret;
+ }
+
+ if (m = str.match(/^\W*rgba?\(([^)]*)\)\W*$/i)) {
+ // rgb(...) or rgba(...) notation
+
+ var par = m[1].split(',');
+ var re = /^\s*(\d+|\d*\.\d+|\d+\.\d*)\s*$/;
+ var mR, mG, mB, mA;
+ if (
+ par.length >= 3 &&
+ (mR = par[0].match(re)) &&
+ (mG = par[1].match(re)) &&
+ (mB = par[2].match(re))
+ ) {
+ ret.format = 'rgb';
+ ret.rgba = [
+ parseFloat(mR[1]) || 0,
+ parseFloat(mG[1]) || 0,
+ parseFloat(mB[1]) || 0,
+ null
+ ];
+
+ if (
+ par.length >= 4 &&
+ (mA = par[3].match(re))
+ ) {
+ ret.format = 'rgba';
+ ret.rgba[3] = parseFloat(mA[1]) || 0;
+ }
+ return ret;
+ }
+ }
+
+ return false;
+ },
+
+
+ parsePaletteValue : function (mixed) {
+ var vals = [];
+
+ if (typeof mixed === 'string') { // input is a string of space separated color values
+ // rgb() and rgba() may contain spaces too, so let's find all color values by regex
+ mixed.replace(/#[0-9A-F]{3}([0-9A-F]{3})?|rgba?\(([^)]*)\)/ig, function (val) {
+ vals.push(val);
+ });
+ } else if (Array.isArray(mixed)) { // input is an array of color values
+ vals = mixed;
+ }
+
+ // convert all values into uniform color format
+
+ var colors = [];
+
+ for (var i = 0; i < vals.length; i++) {
+ var color = jsc.parseColorString(vals[i]);
+ if (color) {
+ colors.push(color);
+ }
+ }
+
+ return colors;
+ },
+
+
+ containsTranparentColor : function (colors) {
+ for (var i = 0; i < colors.length; i++) {
+ var a = colors[i].rgba[3];
+ if (a !== null && a < 1.0) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+
+ isAlphaFormat : function (format) {
+ switch (format.toLowerCase()) {
+ case 'hexa':
+ case 'rgba':
+ return true;
+ }
+ return false;
+ },
+
+
+ // Canvas scaling for retina displays
+ //
+ // adapted from https://www.html5rocks.com/en/tutorials/canvas/hidpi/
+ //
+ scaleCanvasForHighDPR : function (canvas) {
+ var dpr = window.devicePixelRatio || 1;
+ canvas.width *= dpr;
+ canvas.height *= dpr;
+ var ctx = canvas.getContext('2d');
+ ctx.scale(dpr, dpr);
+ },
+
+
+ genColorPreviewCanvas : function (color, separatorPos, specWidth, scaleForHighDPR) {
+
+ var sepW = Math.round(jsc.pub.previewSeparator.length);
+ var sqSize = jsc.pub.chessboardSize;
+ var sqColor1 = jsc.pub.chessboardColor1;
+ var sqColor2 = jsc.pub.chessboardColor2;
+
+ var cWidth = specWidth ? specWidth : sqSize * 2;
+ var cHeight = sqSize * 2;
+
+ var canvas = jsc.createEl('canvas');
+ var ctx = canvas.getContext('2d');
+
+ canvas.width = cWidth;
+ canvas.height = cHeight;
+ if (scaleForHighDPR) {
+ jsc.scaleCanvasForHighDPR(canvas);
+ }
+
+ // transparency chessboard - background
+ ctx.fillStyle = sqColor1;
+ ctx.fillRect(0, 0, cWidth, cHeight);
+
+ // transparency chessboard - squares
+ ctx.fillStyle = sqColor2;
+ for (var x = 0; x < cWidth; x += sqSize * 2) {
+ ctx.fillRect(x, 0, sqSize, sqSize);
+ ctx.fillRect(x + sqSize, sqSize, sqSize, sqSize);
+ }
+
+ if (color) {
+ // actual color in foreground
+ ctx.fillStyle = color;
+ ctx.fillRect(0, 0, cWidth, cHeight);
+ }
+
+ var start = null;
+ switch (separatorPos) {
+ case 'left':
+ start = 0;
+ ctx.clearRect(0, 0, sepW/2, cHeight);
+ break;
+ case 'right':
+ start = cWidth - sepW;
+ ctx.clearRect(cWidth - (sepW/2), 0, sepW/2, cHeight);
+ break;
+ }
+ if (start !== null) {
+ ctx.lineWidth = 1;
+ for (var i = 0; i < jsc.pub.previewSeparator.length; i += 1) {
+ ctx.beginPath();
+ ctx.strokeStyle = jsc.pub.previewSeparator[i];
+ ctx.moveTo(0.5 + start + i, 0);
+ ctx.lineTo(0.5 + start + i, cHeight);
+ ctx.stroke();
+ }
+ }
+
+ return {
+ canvas: canvas,
+ width: cWidth,
+ height: cHeight,
+ };
+ },
+
+
+ // if position or width is not set => fill the entire element (0%-100%)
+ genColorPreviewGradient : function (color, position, width) {
+ var params = [];
+
+ if (position && width) {
+ params = [
+ 'to ' + {'left':'right', 'right':'left'}[position],
+ color + ' 0%',
+ color + ' ' + width + 'px',
+ 'rgba(0,0,0,0) ' + (width + 1) + 'px',
+ 'rgba(0,0,0,0) 100%',
+ ];
+ } else {
+ params = [
+ 'to right',
+ color + ' 0%',
+ color + ' 100%',
+ ];
+ }
+
+ return jsc.linearGradient.apply(this, params);
+ },
+
+
+ redrawPosition : function () {
+
+ if (!jsc.picker || !jsc.picker.owner) {
+ return; // picker is not shown
+ }
+
+ var thisObj = jsc.picker.owner;
+
+ var tp, vp;
+
+ if (thisObj.fixed) {
+ // Fixed elements are positioned relative to viewport,
+ // therefore we can ignore the scroll offset
+ tp = jsc.getElementPos(thisObj.targetElement, true); // target pos
+ vp = [0, 0]; // view pos
+ } else {
+ tp = jsc.getElementPos(thisObj.targetElement); // target pos
+ vp = jsc.getViewPos(); // view pos
+ }
+
+ var ts = jsc.getElementSize(thisObj.targetElement); // target size
+ var vs = jsc.getViewSize(); // view size
+ var pd = jsc.getPickerDims(thisObj);
+ var ps = [pd.borderW, pd.borderH]; // picker outer size
+ var a, b, c;
+ switch (thisObj.position.toLowerCase()) {
+ case 'left': a=1; b=0; c=-1; break;
+ case 'right':a=1; b=0; c=1; break;
+ case 'top': a=0; b=1; c=-1; break;
+ default: a=0; b=1; c=1; break;
+ }
+ var l = (ts[b]+ps[b])/2;
+
+ // compute picker position
+ if (!thisObj.smartPosition) {
+ var pp = [
+ tp[a],
+ tp[b]+ts[b]-l+l*c
+ ];
+ } else {
+ var pp = [
+ -vp[a]+tp[a]+ps[a] > vs[a] ?
+ (-vp[a]+tp[a]+ts[a]/2 > vs[a]/2 && tp[a]+ts[a]-ps[a] >= 0 ? tp[a]+ts[a]-ps[a] : tp[a]) :
+ tp[a],
+ -vp[b]+tp[b]+ts[b]+ps[b]-l+l*c > vs[b] ?
+ (-vp[b]+tp[b]+ts[b]/2 > vs[b]/2 && tp[b]+ts[b]-l-l*c >= 0 ? tp[b]+ts[b]-l-l*c : tp[b]+ts[b]-l+l*c) :
+ (tp[b]+ts[b]-l+l*c >= 0 ? tp[b]+ts[b]-l+l*c : tp[b]+ts[b]-l-l*c)
+ ];
+ }
+
+ var x = pp[a];
+ var y = pp[b];
+ var positionValue = thisObj.fixed ? 'fixed' : 'absolute';
+ var contractShadow =
+ (pp[0] + ps[0] > tp[0] || pp[0] < tp[0] + ts[0]) &&
+ (pp[1] + ps[1] < tp[1] + ts[1]);
+
+ jsc._drawPosition(thisObj, x, y, positionValue, contractShadow);
+ },
+
+
+ _drawPosition : function (thisObj, x, y, positionValue, contractShadow) {
+ var vShadow = contractShadow ? 0 : thisObj.shadowBlur; // px
+
+ jsc.picker.wrap.style.position = positionValue;
+ jsc.picker.wrap.style.left = x + 'px';
+ jsc.picker.wrap.style.top = y + 'px';
+
+ jsc.setBoxShadow(
+ jsc.picker.boxS,
+ thisObj.shadow ?
+ new jsc.BoxShadow(0, vShadow, thisObj.shadowBlur, 0, thisObj.shadowColor) :
+ null);
+ },
+
+
+ getPickerDims : function (thisObj) {
+ var w = 2 * thisObj.controlBorderWidth + thisObj.width;
+ var h = 2 * thisObj.controlBorderWidth + thisObj.height;
+
+ var sliderSpace = 2 * thisObj.controlBorderWidth + 2 * jsc.getControlPadding(thisObj) + thisObj.sliderSize;
+
+ if (jsc.getSliderChannel(thisObj)) {
+ w += sliderSpace;
+ }
+ if (thisObj.hasAlphaChannel()) {
+ w += sliderSpace;
+ }
+
+ var pal = jsc.getPaletteDims(thisObj, w);
+
+ if (pal.height) {
+ h += pal.height + thisObj.padding;
+ }
+ if (thisObj.closeButton) {
+ h += 2 * thisObj.controlBorderWidth + thisObj.padding + thisObj.buttonHeight;
+ }
+
+ var pW = w + (2 * thisObj.padding);
+ var pH = h + (2 * thisObj.padding);
+
+ return {
+ contentW: w,
+ contentH: h,
+ paddedW: pW,
+ paddedH: pH,
+ borderW: pW + (2 * thisObj.borderWidth),
+ borderH: pH + (2 * thisObj.borderWidth),
+ palette: pal,
+ };
+ },
+
+
+ getPaletteDims : function (thisObj, width) {
+ var cols = 0, rows = 0, cellW = 0, cellH = 0, height = 0;
+ var sampleCount = thisObj._palette ? thisObj._palette.length : 0;
+
+ if (sampleCount) {
+ cols = thisObj.paletteCols;
+ rows = cols > 0 ? Math.ceil(sampleCount / cols) : 0;
+
+ // color sample's dimensions (includes border)
+ cellW = Math.max(1, Math.floor((width - ((cols - 1) * thisObj.paletteSpacing)) / cols));
+ cellH = thisObj.paletteHeight ? Math.min(thisObj.paletteHeight, cellW) : cellW;
+ }
+
+ if (rows) {
+ height =
+ rows * cellH +
+ (rows - 1) * thisObj.paletteSpacing;
+ }
+
+ return {
+ cols: cols,
+ rows: rows,
+ cellW: cellW,
+ cellH: cellH,
+ width: width,
+ height: height,
+ };
+ },
+
+
+ getControlPadding : function (thisObj) {
+ return Math.max(
+ thisObj.padding / 2,
+ (2 * thisObj.pointerBorderWidth + thisObj.pointerThickness) - thisObj.controlBorderWidth
+ );
+ },
+
+
+ getPadYChannel : function (thisObj) {
+ switch (thisObj.mode.charAt(1).toLowerCase()) {
+ case 'v': return 'v'; break;
+ }
+ return 's';
+ },
+
+
+ getSliderChannel : function (thisObj) {
+ if (thisObj.mode.length > 2) {
+ switch (thisObj.mode.charAt(2).toLowerCase()) {
+ case 's': return 's'; break;
+ case 'v': return 'v'; break;
+ }
+ }
+ return null;
+ },
+
+
+ // calls function specified in picker's property
+ triggerCallback : function (thisObj, prop) {
+ if (!thisObj[prop]) {
+ return; // callback func not specified
+ }
+ var callback = null;
+
+ if (typeof thisObj[prop] === 'string') {
+ // string with code
+ try {
+ callback = new Function (thisObj[prop]);
+ } catch (e) {
+ console.error(e);
+ }
+ } else {
+ // function
+ callback = thisObj[prop];
+ }
+
+ if (callback) {
+ callback.call(thisObj);
+ }
+ },
+
+
+ // Triggers a color change related event(s) on all picker instances.
+ // It is possible to specify multiple events separated with a space.
+ triggerGlobal : function (eventNames) {
+ var inst = jsc.getInstances();
+ for (var i = 0; i < inst.length; i += 1) {
+ inst[i].trigger(eventNames);
+ }
+ },
+
+
+ _pointerMoveEvent : {
+ mouse: 'mousemove',
+ touch: 'touchmove'
+ },
+ _pointerEndEvent : {
+ mouse: 'mouseup',
+ touch: 'touchend'
+ },
+
+
+ _pointerOrigin : null,
+ _capturedTarget : null,
+
+
+ onDocumentKeyUp : function (e) {
+ if (['Tab', 'Escape'].indexOf(jsc.eventKey(e)) !== -1) {
+ if (jsc.picker && jsc.picker.owner) {
+ jsc.picker.owner.tryHide();
+ }
+ }
+ },
+
+
+ onWindowResize : function (e) {
+ jsc.redrawPosition();
+ },
+
+
+ onWindowScroll : function (e) {
+ jsc.redrawPosition();
+ },
+
+
+ onParentScroll : function (e) {
+ // hide the picker when one of the parent elements is scrolled
+ if (jsc.picker && jsc.picker.owner) {
+ jsc.picker.owner.tryHide();
+ }
+ },
+
+
+ onDocumentMouseDown : function (e) {
+ var target = e.target || e.srcElement;
+
+ if (target.jscolor && target.jscolor instanceof jsc.pub) { // clicked targetElement -> show picker
+ if (target.jscolor.showOnClick && !target.disabled) {
+ target.jscolor.show();
+ }
+ } else if (jsc.getData(target, 'gui')) { // clicked jscolor's GUI element
+ var control = jsc.getData(target, 'control');
+ if (control) {
+ // jscolor's control
+ jsc.onControlPointerStart(e, target, jsc.getData(target, 'control'), 'mouse');
+ }
+ } else {
+ // mouse is outside the picker's controls -> hide the color picker!
+ if (jsc.picker && jsc.picker.owner) {
+ jsc.picker.owner.tryHide();
+ }
+ }
+ },
+
+
+ onPickerTouchStart : function (e) {
+ var target = e.target || e.srcElement;
+
+ if (jsc.getData(target, 'control')) {
+ jsc.onControlPointerStart(e, target, jsc.getData(target, 'control'), 'touch');
+ }
+ },
+
+
+ onControlPointerStart : function (e, target, controlName, pointerType) {
+ var thisObj = jsc.getData(target, 'instance');
+
+ jsc.preventDefault(e);
+ jsc.captureTarget(target);
+
+ var registerDragEvents = function (doc, offset) {
+ jsc.attachGroupEvent('drag', doc, jsc._pointerMoveEvent[pointerType],
+ jsc.onDocumentPointerMove(e, target, controlName, pointerType, offset));
+ jsc.attachGroupEvent('drag', doc, jsc._pointerEndEvent[pointerType],
+ jsc.onDocumentPointerEnd(e, target, controlName, pointerType));
+ };
+
+ registerDragEvents(window.document, [0, 0]);
+
+ if (window.parent && window.frameElement) {
+ var rect = window.frameElement.getBoundingClientRect();
+ var ofs = [-rect.left, -rect.top];
+ registerDragEvents(window.parent.window.document, ofs);
+ }
+
+ var abs = jsc.getAbsPointerPos(e);
+ var rel = jsc.getRelPointerPos(e);
+ jsc._pointerOrigin = {
+ x: abs.x - rel.x,
+ y: abs.y - rel.y
+ };
+
+ switch (controlName) {
+ case 'pad':
+ // if the value slider is at the bottom, move it up
+ if (jsc.getSliderChannel(thisObj) === 'v' && thisObj.channels.v === 0) {
+ thisObj.fromHSVA(null, null, 100, null);
+ }
+ jsc.setPad(thisObj, e, 0, 0);
+ break;
+
+ case 'sld':
+ jsc.setSld(thisObj, e, 0);
+ break;
+
+ case 'asld':
+ jsc.setASld(thisObj, e, 0);
+ break;
+ }
+ thisObj.trigger('input');
+ },
+
+
+ onDocumentPointerMove : function (e, target, controlName, pointerType, offset) {
+ return function (e) {
+ var thisObj = jsc.getData(target, 'instance');
+ switch (controlName) {
+ case 'pad':
+ jsc.setPad(thisObj, e, offset[0], offset[1]);
+ break;
+
+ case 'sld':
+ jsc.setSld(thisObj, e, offset[1]);
+ break;
+
+ case 'asld':
+ jsc.setASld(thisObj, e, offset[1]);
+ break;
+ }
+ thisObj.trigger('input');
+ }
+ },
+
+
+ onDocumentPointerEnd : function (e, target, controlName, pointerType) {
+ return function (e) {
+ var thisObj = jsc.getData(target, 'instance');
+ jsc.detachGroupEvents('drag');
+ jsc.releaseTarget();
+
+ // Always trigger changes AFTER detaching outstanding mouse handlers,
+ // in case some color change that occured in user-defined onChange/onInput handler
+ // intruded into current mouse events
+ thisObj.trigger('input');
+ thisObj.trigger('change');
+ };
+ },
+
+
+ onPaletteSampleClick : function (e) {
+ var target = e.currentTarget;
+ var thisObj = jsc.getData(target, 'instance');
+ var color = jsc.getData(target, 'color');
+
+ // when format is flexible, use the original format of this color sample
+ if (thisObj.format.toLowerCase() === 'any') {
+ thisObj._setFormat(color.format); // adapt format
+ if (!jsc.isAlphaFormat(thisObj.getFormat())) {
+ color.rgba[3] = 1.0; // when switching to a format that doesn't support alpha, set full opacity
+ }
+ }
+
+ // if this color doesn't specify alpha, use alpha of 1.0 (if applicable)
+ if (color.rgba[3] === null) {
+ if (thisObj.paletteSetsAlpha === true || (thisObj.paletteSetsAlpha === 'auto' && thisObj._paletteHasTransparency)) {
+ color.rgba[3] = 1.0;
+ }
+ }
+
+ thisObj.fromRGBA.apply(thisObj, color.rgba);
+
+ thisObj.trigger('input');
+ thisObj.trigger('change');
+
+ if (thisObj.hideOnPaletteClick) {
+ thisObj.hide();
+ }
+ },
+
+
+ setPad : function (thisObj, e, ofsX, ofsY) {
+ var pointerAbs = jsc.getAbsPointerPos(e);
+ var x = ofsX + pointerAbs.x - jsc._pointerOrigin.x - thisObj.padding - thisObj.controlBorderWidth;
+ var y = ofsY + pointerAbs.y - jsc._pointerOrigin.y - thisObj.padding - thisObj.controlBorderWidth;
+
+ var xVal = x * (360 / (thisObj.width - 1));
+ var yVal = 100 - (y * (100 / (thisObj.height - 1)));
+
+ switch (jsc.getPadYChannel(thisObj)) {
+ case 's': thisObj.fromHSVA(xVal, yVal, null, null); break;
+ case 'v': thisObj.fromHSVA(xVal, null, yVal, null); break;
+ }
+ },
+
+
+ setSld : function (thisObj, e, ofsY) {
+ var pointerAbs = jsc.getAbsPointerPos(e);
+ var y = ofsY + pointerAbs.y - jsc._pointerOrigin.y - thisObj.padding - thisObj.controlBorderWidth;
+ var yVal = 100 - (y * (100 / (thisObj.height - 1)));
+
+ switch (jsc.getSliderChannel(thisObj)) {
+ case 's': thisObj.fromHSVA(null, yVal, null, null); break;
+ case 'v': thisObj.fromHSVA(null, null, yVal, null); break;
+ }
+ },
+
+
+ setASld : function (thisObj, e, ofsY) {
+ var pointerAbs = jsc.getAbsPointerPos(e);
+ var y = ofsY + pointerAbs.y - jsc._pointerOrigin.y - thisObj.padding - thisObj.controlBorderWidth;
+ var yVal = 1.0 - (y * (1.0 / (thisObj.height - 1)));
+
+ if (yVal < 1.0) {
+ // if format is flexible and the current format doesn't support alpha, switch to a suitable one
+ var fmt = thisObj.getFormat();
+ if (thisObj.format.toLowerCase() === 'any' && !jsc.isAlphaFormat(fmt)) {
+ thisObj._setFormat(fmt === 'hex' ? 'hexa' : 'rgba');
+ }
+ }
+
+ thisObj.fromHSVA(null, null, null, yVal);
+ },
+
+
+ createPadCanvas : function () {
+
+ var ret = {
+ elm: null,
+ draw: null
+ };
+
+ var canvas = jsc.createEl('canvas');
+ var ctx = canvas.getContext('2d');
+
+ var drawFunc = function (width, height, type) {
+ canvas.width = width;
+ canvas.height = height;
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ var hGrad = ctx.createLinearGradient(0, 0, canvas.width, 0);
+ hGrad.addColorStop(0 / 6, '#F00');
+ hGrad.addColorStop(1 / 6, '#FF0');
+ hGrad.addColorStop(2 / 6, '#0F0');
+ hGrad.addColorStop(3 / 6, '#0FF');
+ hGrad.addColorStop(4 / 6, '#00F');
+ hGrad.addColorStop(5 / 6, '#F0F');
+ hGrad.addColorStop(6 / 6, '#F00');
+
+ ctx.fillStyle = hGrad;
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+ var vGrad = ctx.createLinearGradient(0, 0, 0, canvas.height);
+ switch (type.toLowerCase()) {
+ case 's':
+ vGrad.addColorStop(0, 'rgba(255,255,255,0)');
+ vGrad.addColorStop(1, 'rgba(255,255,255,1)');
+ break;
+ case 'v':
+ vGrad.addColorStop(0, 'rgba(0,0,0,0)');
+ vGrad.addColorStop(1, 'rgba(0,0,0,1)');
+ break;
+ }
+ ctx.fillStyle = vGrad;
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+ };
+
+ ret.elm = canvas;
+ ret.draw = drawFunc;
+
+ return ret;
+ },
+
+
+ createSliderGradient : function () {
+
+ var ret = {
+ elm: null,
+ draw: null
+ };
+
+ var canvas = jsc.createEl('canvas');
+ var ctx = canvas.getContext('2d');
+
+ var drawFunc = function (width, height, color1, color2) {
+ canvas.width = width;
+ canvas.height = height;
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ var grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
+ grad.addColorStop(0, color1);
+ grad.addColorStop(1, color2);
+
+ ctx.fillStyle = grad;
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+ };
+
+ ret.elm = canvas;
+ ret.draw = drawFunc;
+
+ return ret;
+ },
+
+
+ createASliderGradient : function () {
+
+ var ret = {
+ elm: null,
+ draw: null
+ };
+
+ var canvas = jsc.createEl('canvas');
+ var ctx = canvas.getContext('2d');
+
+ var drawFunc = function (width, height, color) {
+ canvas.width = width;
+ canvas.height = height;
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ var sqSize = canvas.width / 2;
+ var sqColor1 = jsc.pub.chessboardColor1;
+ var sqColor2 = jsc.pub.chessboardColor2;
+
+ // dark gray background
+ ctx.fillStyle = sqColor1;
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+ if (sqSize > 0) { // to avoid infinite loop
+ for (var y = 0; y < canvas.height; y += sqSize * 2) {
+ // light gray squares
+ ctx.fillStyle = sqColor2;
+ ctx.fillRect(0, y, sqSize, sqSize);
+ ctx.fillRect(sqSize, y + sqSize, sqSize, sqSize);
+ }
+ }
+
+ var grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
+ grad.addColorStop(0, color);
+ grad.addColorStop(1, 'rgba(0,0,0,0)');
+
+ ctx.fillStyle = grad;
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+ };
+
+ ret.elm = canvas;
+ ret.draw = drawFunc;
+
+ return ret;
+ },
+
+
+ BoxShadow : (function () {
+ var BoxShadow = function (hShadow, vShadow, blur, spread, color, inset) {
+ this.hShadow = hShadow;
+ this.vShadow = vShadow;
+ this.blur = blur;
+ this.spread = spread;
+ this.color = color;
+ this.inset = !!inset;
+ };
+
+ BoxShadow.prototype.toString = function () {
+ var vals = [
+ Math.round(this.hShadow) + 'px',
+ Math.round(this.vShadow) + 'px',
+ Math.round(this.blur) + 'px',
+ Math.round(this.spread) + 'px',
+ this.color
+ ];
+ if (this.inset) {
+ vals.push('inset');
+ }
+ return vals.join(' ');
+ };
+
+ return BoxShadow;
+ })(),
+
+
+ flags : {
+ leaveValue : 1 << 0,
+ leaveAlpha : 1 << 1,
+ leavePreview : 1 << 2,
+ },
+
+
+ enumOpts : {
+ format: ['auto', 'any', 'hex', 'hexa', 'rgb', 'rgba'],
+ previewPosition: ['left', 'right'],
+ mode: ['hsv', 'hvs', 'hs', 'hv'],
+ position: ['left', 'right', 'top', 'bottom'],
+ alphaChannel: ['auto', true, false],
+ paletteSetsAlpha: ['auto', true, false],
+ },
+
+
+ deprecatedOpts : {
+ // : ( can be null)
+ 'styleElement': 'previewElement',
+ 'onFineChange': 'onInput',
+ 'overwriteImportant': 'forceStyle',
+ 'closable': 'closeButton',
+ 'insetWidth': 'controlBorderWidth',
+ 'insetColor': 'controlBorderColor',
+ 'refine': null,
+ },
+
+
+ docsRef : ' ' + 'See https://jscolor.com/docs/',
+
+
+ //
+ // Usage:
+ // var myPicker = new JSColor( [, ])
+ //
+ // (constructor is accessible via both 'jscolor' and 'JSColor' name)
+ //
+
+ pub : function (targetElement, opts) {
+
+ var THIS = this;
+
+ if (!opts) {
+ opts = {};
+ }
+
+ this.channels = {
+ r: 255, // red [0-255]
+ g: 255, // green [0-255]
+ b: 255, // blue [0-255]
+ h: 0, // hue [0-360]
+ s: 0, // saturation [0-100]
+ v: 100, // value (brightness) [0-100]
+ a: 1.0, // alpha (opacity) [0.0 - 1.0]
+ };
+
+ // General options
+ //
+ this.format = 'auto'; // 'auto' | 'any' | 'hex' | 'hexa' | 'rgb' | 'rgba' - Format of the input/output value
+ this.value = undefined; // INITIAL color value in any supported format. To change it later, use method fromString(), fromHSVA(), fromRGBA() or channel()
+ this.alpha = undefined; // INITIAL alpha value. To change it later, call method channel('A', )
+ this.onChange = undefined; // called when color changes. Value can be either a function or a string with JS code.
+ this.onInput = undefined; // called repeatedly as the color is being changed, e.g. while dragging a slider. Value can be either a function or a string with JS code.
+ this.valueElement = undefined; // element that will be used to display and input the color value
+ this.alphaElement = undefined; // element that will be used to display and input the alpha (opacity) value
+ this.previewElement = undefined; // element that will preview the picked color using CSS background
+ this.previewPosition = 'left'; // 'left' | 'right' - position of the color preview in previewElement
+ this.previewSize = 32; // (px) width of the color preview displayed in previewElement
+ this.previewPadding = 8; // (px) space between color preview and content of the previewElement
+ this.required = true; // whether the associated text input must always contain a color value. If false, the input can be left empty.
+ this.hash = true; // whether to prefix the HEX color code with # symbol (only applicable for HEX format)
+ this.uppercase = true; // whether to show the HEX color code in upper case (only applicable for HEX format)
+ this.forceStyle = true; // whether to overwrite CSS style of the previewElement using !important flag
+
+ // Color Picker options
+ //
+ this.width = 181; // width of the color spectrum (in px)
+ this.height = 101; // height of the color spectrum (in px)
+ this.mode = 'HSV'; // 'HSV' | 'HVS' | 'HS' | 'HV' - layout of the color picker controls
+ this.alphaChannel = 'auto'; // 'auto' | true | false - if alpha channel is enabled, the alpha slider will be visible. If 'auto', it will be determined according to color format
+ this.position = 'bottom'; // 'left' | 'right' | 'top' | 'bottom' - position relative to the target element
+ this.smartPosition = true; // automatically change picker position when there is not enough space for it
+ this.showOnClick = true; // whether to show the picker when user clicks its target element
+ this.hideOnLeave = true; // whether to automatically hide the picker when user leaves its target element (e.g. upon clicking the document)
+ this.palette = []; // colors to be displayed in the palette, specified as an array or a string of space separated color values (in any supported format)
+ this.paletteCols = 10; // number of columns in the palette
+ this.paletteSetsAlpha = 'auto'; // 'auto' | true | false - if true, palette colors that don't specify alpha will set alpha to 1.0
+ this.paletteHeight = 16; // maximum height (px) of a row in the palette
+ this.paletteSpacing = 4; // distance (px) between color samples in the palette
+ this.hideOnPaletteClick = false; // when set to true, clicking the palette will also hide the color picker
+ this.sliderSize = 16; // px
+ this.crossSize = 8; // px
+ this.closeButton = false; // whether to display the Close button
+ this.closeText = 'Close';
+ this.buttonColor = 'rgba(0,0,0,1)'; // CSS color
+ this.buttonHeight = 18; // px
+ this.padding = 12; // px
+ this.backgroundColor = 'rgba(255,255,255,1)'; // CSS color
+ this.borderWidth = 1; // px
+ this.borderColor = 'rgba(187,187,187,1)'; // CSS color
+ this.borderRadius = 8; // px
+ this.controlBorderWidth = 1; // px
+ this.controlBorderColor = 'rgba(187,187,187,1)'; // CSS color
+ this.shadow = true; // whether to display a shadow
+ this.shadowBlur = 15; // px
+ this.shadowColor = 'rgba(0,0,0,0.2)'; // CSS color
+ this.pointerColor = 'rgba(76,76,76,1)'; // CSS color
+ this.pointerBorderWidth = 1; // px
+ this.pointerBorderColor = 'rgba(255,255,255,1)'; // CSS color
+ this.pointerThickness = 2; // px
+ this.zIndex = 5000;
+ this.container = undefined; // where to append the color picker (BODY element by default)
+
+ // Experimental
+ //
+ this.minS = 0; // min allowed saturation (0 - 100)
+ this.maxS = 100; // max allowed saturation (0 - 100)
+ this.minV = 0; // min allowed value (brightness) (0 - 100)
+ this.maxV = 100; // max allowed value (brightness) (0 - 100)
+ this.minA = 0.0; // min allowed alpha (opacity) (0.0 - 1.0)
+ this.maxA = 1.0; // max allowed alpha (opacity) (0.0 - 1.0)
+
+
+ // Getter: option(name)
+ // Setter: option(name, value)
+ // option({name:value, ...})
+ //
+ this.option = function () {
+ if (!arguments.length) {
+ throw new Error('No option specified');
+ }
+
+ if (arguments.length === 1 && typeof arguments[0] === 'string') {
+ // getting a single option
+ try {
+ return getOption(arguments[0]);
+ } catch (e) {
+ console.warn(e);
+ }
+ return false;
+
+ } else if (arguments.length >= 2 && typeof arguments[0] === 'string') {
+ // setting a single option
+ try {
+ if (!setOption(arguments[0], arguments[1])) {
+ return false;
+ }
+ } catch (e) {
+ console.warn(e);
+ return false;
+ }
+ this.redraw(); // immediately redraws the picker, if it's displayed
+ this.exposeColor(); // in case some preview-related or format-related option was changed
+ return true;
+
+ } else if (arguments.length === 1 && typeof arguments[0] === 'object') {
+ // setting multiple options
+ var opts = arguments[0];
+ var success = true;
+ for (var opt in opts) {
+ if (opts.hasOwnProperty(opt)) {
+ try {
+ if (!setOption(opt, opts[opt])) {
+ success = false;
+ }
+ } catch (e) {
+ console.warn(e);
+ success = false;
+ }
+ }
+ }
+ this.redraw(); // immediately redraws the picker, if it's displayed
+ this.exposeColor(); // in case some preview-related or format-related option was changed
+ return success;
+ }
+
+ throw new Error('Invalid arguments');
+ }
+
+
+ // Getter: channel(name)
+ // Setter: channel(name, value)
+ //
+ this.channel = function (name, value) {
+ if (typeof name !== 'string') {
+ throw new Error('Invalid value for channel name: ' + name);
+ }
+
+ if (value === undefined) {
+ // getting channel value
+ if (!this.channels.hasOwnProperty(name.toLowerCase())) {
+ console.warn('Getting unknown channel: ' + name);
+ return false;
+ }
+ return this.channels[name.toLowerCase()];
+
+ } else {
+ // setting channel value
+ var res = false;
+ switch (name.toLowerCase()) {
+ case 'r': res = this.fromRGBA(value, null, null, null); break;
+ case 'g': res = this.fromRGBA(null, value, null, null); break;
+ case 'b': res = this.fromRGBA(null, null, value, null); break;
+ case 'h': res = this.fromHSVA(value, null, null, null); break;
+ case 's': res = this.fromHSVA(null, value, null, null); break;
+ case 'v': res = this.fromHSVA(null, null, value, null); break;
+ case 'a': res = this.fromHSVA(null, null, null, value); break;
+ default:
+ console.warn('Setting unknown channel: ' + name);
+ return false;
+ }
+ if (res) {
+ this.redraw(); // immediately redraws the picker, if it's displayed
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+
+ // Triggers given input event(s) by:
+ // - executing on callback specified as picker's option
+ // - triggering standard DOM event listeners attached to the value element
+ //
+ // It is possible to specify multiple events separated with a space.
+ //
+ this.trigger = function (eventNames) {
+ var evs = jsc.strList(eventNames);
+ for (var i = 0; i < evs.length; i += 1) {
+ var ev = evs[i].toLowerCase();
+
+ // trigger a callback
+ var callbackProp = null;
+ switch (ev) {
+ case 'input': callbackProp = 'onInput'; break;
+ case 'change': callbackProp = 'onChange'; break;
+ }
+ if (callbackProp) {
+ jsc.triggerCallback(this, callbackProp);
+ }
+
+ // trigger standard DOM event listeners on the value element
+ jsc.triggerInputEvent(this.valueElement, ev, true, true);
+ }
+ };
+
+
+ // h: 0-360
+ // s: 0-100
+ // v: 0-100
+ // a: 0.0-1.0
+ //
+ this.fromHSVA = function (h, s, v, a, flags) { // null = don't change
+ if (h === undefined) { h = null; }
+ if (s === undefined) { s = null; }
+ if (v === undefined) { v = null; }
+ if (a === undefined) { a = null; }
+
+ if (h !== null) {
+ if (isNaN(h)) { return false; }
+ this.channels.h = Math.max(0, Math.min(360, h));
+ }
+ if (s !== null) {
+ if (isNaN(s)) { return false; }
+ this.channels.s = Math.max(0, Math.min(100, this.maxS, s), this.minS);
+ }
+ if (v !== null) {
+ if (isNaN(v)) { return false; }
+ this.channels.v = Math.max(0, Math.min(100, this.maxV, v), this.minV);
+ }
+ if (a !== null) {
+ if (isNaN(a)) { return false; }
+ this.channels.a = this.hasAlphaChannel() ?
+ Math.max(0, Math.min(1, this.maxA, a), this.minA) :
+ 1.0; // if alpha channel is disabled, the color should stay 100% opaque
+ }
+
+ var rgb = jsc.HSV_RGB(
+ this.channels.h,
+ this.channels.s,
+ this.channels.v
+ );
+ this.channels.r = rgb[0];
+ this.channels.g = rgb[1];
+ this.channels.b = rgb[2];
+
+ this.exposeColor(flags);
+ return true;
+ };
+
+
+ // r: 0-255
+ // g: 0-255
+ // b: 0-255
+ // a: 0.0-1.0
+ //
+ this.fromRGBA = function (r, g, b, a, flags) { // null = don't change
+ if (r === undefined) { r = null; }
+ if (g === undefined) { g = null; }
+ if (b === undefined) { b = null; }
+ if (a === undefined) { a = null; }
+
+ if (r !== null) {
+ if (isNaN(r)) { return false; }
+ r = Math.max(0, Math.min(255, r));
+ }
+ if (g !== null) {
+ if (isNaN(g)) { return false; }
+ g = Math.max(0, Math.min(255, g));
+ }
+ if (b !== null) {
+ if (isNaN(b)) { return false; }
+ b = Math.max(0, Math.min(255, b));
+ }
+ if (a !== null) {
+ if (isNaN(a)) { return false; }
+ this.channels.a = this.hasAlphaChannel() ?
+ Math.max(0, Math.min(1, this.maxA, a), this.minA) :
+ 1.0; // if alpha channel is disabled, the color should stay 100% opaque
+ }
+
+ var hsv = jsc.RGB_HSV(
+ r===null ? this.channels.r : r,
+ g===null ? this.channels.g : g,
+ b===null ? this.channels.b : b
+ );
+ if (hsv[0] !== null) {
+ this.channels.h = Math.max(0, Math.min(360, hsv[0]));
+ }
+ if (hsv[2] !== 0) { // fully black color stays black through entire saturation range, so let's not change saturation
+ this.channels.s = Math.max(0, this.minS, Math.min(100, this.maxS, hsv[1]));
+ }
+ this.channels.v = Math.max(0, this.minV, Math.min(100, this.maxV, hsv[2]));
+
+ // update RGB according to final HSV, as some values might be trimmed
+ var rgb = jsc.HSV_RGB(this.channels.h, this.channels.s, this.channels.v);
+ this.channels.r = rgb[0];
+ this.channels.g = rgb[1];
+ this.channels.b = rgb[2];
+
+ this.exposeColor(flags);
+ return true;
+ };
+
+
+ // DEPRECATED. Use .fromHSVA() instead
+ //
+ this.fromHSV = function (h, s, v, flags) {
+ console.warn('fromHSV() method is DEPRECATED. Using fromHSVA() instead.' + jsc.docsRef);
+ return this.fromHSVA(h, s, v, null, flags);
+ };
+
+
+ // DEPRECATED. Use .fromRGBA() instead
+ //
+ this.fromRGB = function (r, g, b, flags) {
+ console.warn('fromRGB() method is DEPRECATED. Using fromRGBA() instead.' + jsc.docsRef);
+ return this.fromRGBA(r, g, b, null, flags);
+ };
+
+
+ this.fromString = function (str, flags) {
+ if (!this.required && str.trim() === '') {
+ // setting empty string to an optional color input
+ this.setPreviewElementBg(null);
+ this.setValueElementValue('');
+ return true;
+ }
+
+ var color = jsc.parseColorString(str);
+ if (!color) {
+ return false; // could not parse
+ }
+ if (this.format.toLowerCase() === 'any') {
+ this._setFormat(color.format); // adapt format
+ if (!jsc.isAlphaFormat(this.getFormat())) {
+ color.rgba[3] = 1.0; // when switching to a format that doesn't support alpha, set full opacity
+ }
+ }
+ this.fromRGBA(
+ color.rgba[0],
+ color.rgba[1],
+ color.rgba[2],
+ color.rgba[3],
+ flags
+ );
+ return true;
+ };
+
+
+ this.toString = function (format) {
+ if (format === undefined) {
+ format = this.getFormat(); // format not specified -> use the current format
+ }
+ switch (format.toLowerCase()) {
+ case 'hex': return this.toHEXString(); break;
+ case 'hexa': return this.toHEXAString(); break;
+ case 'rgb': return this.toRGBString(); break;
+ case 'rgba': return this.toRGBAString(); break;
+ }
+ return false;
+ };
+
+
+ this.toHEXString = function () {
+ return jsc.hexColor(
+ this.channels.r,
+ this.channels.g,
+ this.channels.b
+ );
+ };
+
+
+ this.toHEXAString = function () {
+ return jsc.hexaColor(
+ this.channels.r,
+ this.channels.g,
+ this.channels.b,
+ this.channels.a
+ );
+ };
+
+
+ this.toRGBString = function () {
+ return jsc.rgbColor(
+ this.channels.r,
+ this.channels.g,
+ this.channels.b
+ );
+ };
+
+
+ this.toRGBAString = function () {
+ return jsc.rgbaColor(
+ this.channels.r,
+ this.channels.g,
+ this.channels.b,
+ this.channels.a
+ );
+ };
+
+
+ this.toGrayscale = function () {
+ return (
+ 0.213 * this.channels.r +
+ 0.715 * this.channels.g +
+ 0.072 * this.channels.b
+ );
+ };
+
+
+ this.toCanvas = function () {
+ return jsc.genColorPreviewCanvas(this.toRGBAString()).canvas;
+ };
+
+
+ this.toDataURL = function () {
+ return this.toCanvas().toDataURL();
+ };
+
+
+ this.toBackground = function () {
+ return jsc.pub.background(this.toRGBAString());
+ };
+
+
+ this.isLight = function () {
+ return this.toGrayscale() > 255 / 2;
+ };
+
+
+ this.hide = function () {
+ if (isPickerOwner()) {
+ detachPicker();
+ }
+ };
+
+
+ this.show = function () {
+ drawPicker();
+ };
+
+
+ this.redraw = function () {
+ if (isPickerOwner()) {
+ drawPicker();
+ }
+ };
+
+
+ this.getFormat = function () {
+ return this._currentFormat;
+ };
+
+
+ this._setFormat = function (format) {
+ this._currentFormat = format.toLowerCase();
+ };
+
+
+ this.hasAlphaChannel = function () {
+ if (this.alphaChannel === 'auto') {
+ return (
+ this.format.toLowerCase() === 'any' || // format can change on the fly (e.g. from hex to rgba), so let's consider the alpha channel enabled
+ jsc.isAlphaFormat(this.getFormat()) || // the current format supports alpha channel
+ this.alpha !== undefined || // initial alpha value is set, so we're working with alpha channel
+ this.alphaElement !== undefined // the alpha value is redirected, so we're working with alpha channel
+ );
+ }
+
+ return this.alphaChannel; // the alpha channel is explicitly set
+ };
+
+
+ this.processValueInput = function (str) {
+ if (!this.fromString(str)) {
+ // could not parse the color value - let's just expose the current color
+ this.exposeColor();
+ }
+ };
+
+
+ this.processAlphaInput = function (str) {
+ if (!this.fromHSVA(null, null, null, parseFloat(str))) {
+ // could not parse the alpha value - let's just expose the current color
+ this.exposeColor();
+ }
+ };
+
+
+ this.exposeColor = function (flags) {
+ var colorStr = this.toString();
+ var fmt = this.getFormat();
+
+ // reflect current color in data- attribute
+ jsc.setDataAttr(this.targetElement, 'current-color', colorStr);
+
+ if (!(flags & jsc.flags.leaveValue) && this.valueElement) {
+ if (fmt === 'hex' || fmt === 'hexa') {
+ if (!this.uppercase) { colorStr = colorStr.toLowerCase(); }
+ if (!this.hash) { colorStr = colorStr.replace(/^#/, ''); }
+ }
+ this.setValueElementValue(colorStr);
+ }
+
+ if (!(flags & jsc.flags.leaveAlpha) && this.alphaElement) {
+ var alphaVal = Math.round(this.channels.a * 100) / 100;
+ this.setAlphaElementValue(alphaVal);
+ }
+
+ if (!(flags & jsc.flags.leavePreview) && this.previewElement) {
+ var previewPos = null; // 'left' | 'right' (null -> fill the entire element)
+
+ if (
+ jsc.isTextInput(this.previewElement) || // text input
+ (jsc.isButton(this.previewElement) && !jsc.isButtonEmpty(this.previewElement)) // button with text
+ ) {
+ previewPos = this.previewPosition;
+ }
+
+ this.setPreviewElementBg(this.toRGBAString());
+ }
+
+ if (isPickerOwner()) {
+ redrawPad();
+ redrawSld();
+ redrawASld();
+ }
+ };
+
+
+ this.setPreviewElementBg = function (color) {
+ if (!this.previewElement) {
+ return;
+ }
+
+ var position = null; // color preview position: null | 'left' | 'right'
+ var width = null; // color preview width: px | null = fill the entire element
+ if (
+ jsc.isTextInput(this.previewElement) || // text input
+ (jsc.isButton(this.previewElement) && !jsc.isButtonEmpty(this.previewElement)) // button with text
+ ) {
+ position = this.previewPosition;
+ width = this.previewSize;
+ }
+
+ var backgrounds = [];
+
+ if (!color) {
+ // there is no color preview to display -> let's remove any previous background image
+ backgrounds.push({
+ image: 'none',
+ position: 'left top',
+ size: 'auto',
+ repeat: 'no-repeat',
+ origin: 'padding-box',
+ });
+ } else {
+ // CSS gradient for background color preview
+ backgrounds.push({
+ image: jsc.genColorPreviewGradient(
+ color,
+ position,
+ width ? width - jsc.pub.previewSeparator.length : null
+ ),
+ position: 'left top',
+ size: 'auto',
+ repeat: position ? 'repeat-y' : 'repeat',
+ origin: 'padding-box',
+ });
+
+ // data URL of generated PNG image with a gray transparency chessboard
+ var preview = jsc.genColorPreviewCanvas(
+ 'rgba(0,0,0,0)',
+ position ? {'left':'right', 'right':'left'}[position] : null,
+ width,
+ true
+ );
+ backgrounds.push({
+ image: 'url(\'' + preview.canvas.toDataURL() + '\')',
+ position: (position || 'left') + ' top',
+ size: preview.width + 'px ' + preview.height + 'px',
+ repeat: position ? 'repeat-y' : 'repeat',
+ origin: 'padding-box',
+ });
+ }
+
+ var bg = {
+ image: [],
+ position: [],
+ size: [],
+ repeat: [],
+ origin: [],
+ };
+ for (var i = 0; i < backgrounds.length; i += 1) {
+ bg.image.push(backgrounds[i].image);
+ bg.position.push(backgrounds[i].position);
+ bg.size.push(backgrounds[i].size);
+ bg.repeat.push(backgrounds[i].repeat);
+ bg.origin.push(backgrounds[i].origin);
+ }
+
+ // set previewElement's background-images
+ var sty = {
+ 'background-image': bg.image.join(', '),
+ 'background-position': bg.position.join(', '),
+ 'background-size': bg.size.join(', '),
+ 'background-repeat': bg.repeat.join(', '),
+ 'background-origin': bg.origin.join(', '),
+ };
+ jsc.setStyle(this.previewElement, sty, this.forceStyle);
+
+
+ // set/restore previewElement's padding
+ var padding = {
+ left: null,
+ right: null,
+ };
+ if (position) {
+ padding[position] = (this.previewSize + this.previewPadding) + 'px';
+ }
+
+ var sty = {
+ 'padding-left': padding.left,
+ 'padding-right': padding.right,
+ };
+ jsc.setStyle(this.previewElement, sty, this.forceStyle, true);
+ };
+
+
+ this.setValueElementValue = function (str) {
+ if (this.valueElement) {
+ if (jsc.nodeName(this.valueElement) === 'input') {
+ this.valueElement.value = str;
+ } else {
+ this.valueElement.innerHTML = str;
+ }
+ }
+ };
+
+
+ this.setAlphaElementValue = function (str) {
+ if (this.alphaElement) {
+ if (jsc.nodeName(this.alphaElement) === 'input') {
+ this.alphaElement.value = str;
+ } else {
+ this.alphaElement.innerHTML = str;
+ }
+ }
+ };
+
+
+ this._processParentElementsInDOM = function () {
+ if (this._parentElementsProcessed) { return; }
+ this._parentElementsProcessed = true;
+
+ var elm = this.targetElement;
+ do {
+ // If the target element or one of its parent nodes has fixed position,
+ // then use fixed positioning instead
+ var compStyle = jsc.getCompStyle(elm);
+ if (compStyle.position && compStyle.position.toLowerCase() === 'fixed') {
+ this.fixed = true;
+ }
+
+ if (elm !== this.targetElement) {
+ // Ensure to attach onParentScroll only once to each parent element
+ // (multiple targetElements can share the same parent nodes)
+ //
+ // Note: It's not just offsetParents that can be scrollable,
+ // that's why we loop through all parent nodes
+ if (!jsc.getData(elm, 'hasScrollListener')) {
+ elm.addEventListener('scroll', jsc.onParentScroll, false);
+ jsc.setData(elm, 'hasScrollListener', true);
+ }
+ }
+ } while ((elm = elm.parentNode) && jsc.nodeName(elm) !== 'body');
+ };
+
+
+ this.tryHide = function () {
+ if (this.hideOnLeave) {
+ this.hide();
+ }
+ };
+
+
+ this.set__palette = function (val) {
+ this.palette = val;
+ this._palette = jsc.parsePaletteValue(val);
+ this._paletteHasTransparency = jsc.containsTranparentColor(this._palette);
+ };
+
+
+ function setOption (option, value) {
+ if (typeof option !== 'string') {
+ throw new Error('Invalid value for option name: ' + option);
+ }
+
+ // enum option
+ if (jsc.enumOpts.hasOwnProperty(option)) {
+ if (typeof value === 'string') { // enum string values are case insensitive
+ value = value.toLowerCase();
+ }
+ if (jsc.enumOpts[option].indexOf(value) === -1) {
+ throw new Error('Option \'' + option + '\' has invalid value: ' + value);
+ }
+ }
+
+ // deprecated option
+ if (jsc.deprecatedOpts.hasOwnProperty(option)) {
+ var oldOpt = option;
+ var newOpt = jsc.deprecatedOpts[option];
+ if (newOpt) {
+ // if we have a new name for this option, let's log a warning and use the new name
+ console.warn('Option \'%s\' is DEPRECATED, using \'%s\' instead.' + jsc.docsRef, oldOpt, newOpt);
+ option = newOpt;
+ } else {
+ // new name not available for the option
+ throw new Error('Option \'' + option + '\' is DEPRECATED');
+ }
+ }
+
+ var setter = 'set__' + option;
+
+ if (typeof THIS[setter] === 'function') { // a setter exists for this option
+ THIS[setter](value);
+ return true;
+
+ } else if (option in THIS) { // option exists as a property
+ THIS[option] = value;
+ return true;
+ }
+
+ throw new Error('Unrecognized configuration option: ' + option);
+ }
+
+
+ function getOption (option) {
+ if (typeof option !== 'string') {
+ throw new Error('Invalid value for option name: ' + option);
+ }
+
+ // deprecated option
+ if (jsc.deprecatedOpts.hasOwnProperty(option)) {
+ var oldOpt = option;
+ var newOpt = jsc.deprecatedOpts[option];
+ if (newOpt) {
+ // if we have a new name for this option, let's log a warning and use the new name
+ console.warn('Option \'%s\' is DEPRECATED, using \'%s\' instead.' + jsc.docsRef, oldOpt, newOpt);
+ option = newOpt;
+ } else {
+ // new name not available for the option
+ throw new Error('Option \'' + option + '\' is DEPRECATED');
+ }
+ }
+
+ var getter = 'get__' + option;
+
+ if (typeof THIS[getter] === 'function') { // a getter exists for this option
+ return THIS[getter](value);
+
+ } else if (option in THIS) { // option exists as a property
+ return THIS[option];
+ }
+
+ throw new Error('Unrecognized configuration option: ' + option);
+ }
+
+
+ function detachPicker () {
+ jsc.removeClass(THIS.targetElement, jsc.pub.activeClassName);
+ jsc.picker.wrap.parentNode.removeChild(jsc.picker.wrap);
+ delete jsc.picker.owner;
+ }
+
+
+ function drawPicker () {
+
+ // At this point, when drawing the picker, we know what the parent elements are
+ // and we can do all related DOM operations, such as registering events on them
+ // or checking their positioning
+ THIS._processParentElementsInDOM();
+
+ if (!jsc.picker) {
+ jsc.picker = {
+ owner: null, // owner picker instance
+ wrap : jsc.createEl('div'),
+ box : jsc.createEl('div'),
+ boxS : jsc.createEl('div'), // shadow area
+ boxB : jsc.createEl('div'), // border
+ pad : jsc.createEl('div'),
+ padB : jsc.createEl('div'), // border
+ padM : jsc.createEl('div'), // mouse/touch area
+ padCanvas : jsc.createPadCanvas(),
+ cross : jsc.createEl('div'),
+ crossBY : jsc.createEl('div'), // border Y
+ crossBX : jsc.createEl('div'), // border X
+ crossLY : jsc.createEl('div'), // line Y
+ crossLX : jsc.createEl('div'), // line X
+ sld : jsc.createEl('div'), // slider
+ sldB : jsc.createEl('div'), // border
+ sldM : jsc.createEl('div'), // mouse/touch area
+ sldGrad : jsc.createSliderGradient(),
+ sldPtrS : jsc.createEl('div'), // slider pointer spacer
+ sldPtrIB : jsc.createEl('div'), // slider pointer inner border
+ sldPtrMB : jsc.createEl('div'), // slider pointer middle border
+ sldPtrOB : jsc.createEl('div'), // slider pointer outer border
+ asld : jsc.createEl('div'), // alpha slider
+ asldB : jsc.createEl('div'), // border
+ asldM : jsc.createEl('div'), // mouse/touch area
+ asldGrad : jsc.createASliderGradient(),
+ asldPtrS : jsc.createEl('div'), // slider pointer spacer
+ asldPtrIB : jsc.createEl('div'), // slider pointer inner border
+ asldPtrMB : jsc.createEl('div'), // slider pointer middle border
+ asldPtrOB : jsc.createEl('div'), // slider pointer outer border
+ pal : jsc.createEl('div'), // palette
+ btn : jsc.createEl('div'),
+ btnT : jsc.createEl('span'), // text
+ };
+
+ jsc.picker.pad.appendChild(jsc.picker.padCanvas.elm);
+ jsc.picker.padB.appendChild(jsc.picker.pad);
+ jsc.picker.cross.appendChild(jsc.picker.crossBY);
+ jsc.picker.cross.appendChild(jsc.picker.crossBX);
+ jsc.picker.cross.appendChild(jsc.picker.crossLY);
+ jsc.picker.cross.appendChild(jsc.picker.crossLX);
+ jsc.picker.padB.appendChild(jsc.picker.cross);
+ jsc.picker.box.appendChild(jsc.picker.padB);
+ jsc.picker.box.appendChild(jsc.picker.padM);
+
+ jsc.picker.sld.appendChild(jsc.picker.sldGrad.elm);
+ jsc.picker.sldB.appendChild(jsc.picker.sld);
+ jsc.picker.sldB.appendChild(jsc.picker.sldPtrOB);
+ jsc.picker.sldPtrOB.appendChild(jsc.picker.sldPtrMB);
+ jsc.picker.sldPtrMB.appendChild(jsc.picker.sldPtrIB);
+ jsc.picker.sldPtrIB.appendChild(jsc.picker.sldPtrS);
+ jsc.picker.box.appendChild(jsc.picker.sldB);
+ jsc.picker.box.appendChild(jsc.picker.sldM);
+
+ jsc.picker.asld.appendChild(jsc.picker.asldGrad.elm);
+ jsc.picker.asldB.appendChild(jsc.picker.asld);
+ jsc.picker.asldB.appendChild(jsc.picker.asldPtrOB);
+ jsc.picker.asldPtrOB.appendChild(jsc.picker.asldPtrMB);
+ jsc.picker.asldPtrMB.appendChild(jsc.picker.asldPtrIB);
+ jsc.picker.asldPtrIB.appendChild(jsc.picker.asldPtrS);
+ jsc.picker.box.appendChild(jsc.picker.asldB);
+ jsc.picker.box.appendChild(jsc.picker.asldM);
+
+ jsc.picker.box.appendChild(jsc.picker.pal);
+
+ jsc.picker.btn.appendChild(jsc.picker.btnT);
+ jsc.picker.box.appendChild(jsc.picker.btn);
+
+ jsc.picker.boxB.appendChild(jsc.picker.box);
+ jsc.picker.wrap.appendChild(jsc.picker.boxS);
+ jsc.picker.wrap.appendChild(jsc.picker.boxB);
+
+ jsc.picker.wrap.addEventListener('touchstart', jsc.onPickerTouchStart,
+ jsc.isPassiveEventSupported ? {passive: false} : false);
+ }
+
+ var p = jsc.picker;
+
+ var displaySlider = !!jsc.getSliderChannel(THIS);
+ var displayAlphaSlider = THIS.hasAlphaChannel();
+ var pickerDims = jsc.getPickerDims(THIS);
+ var crossOuterSize = (2 * THIS.pointerBorderWidth + THIS.pointerThickness + 2 * THIS.crossSize);
+ var controlPadding = jsc.getControlPadding(THIS);
+ var borderRadius = Math.min(
+ THIS.borderRadius,
+ Math.round(THIS.padding * Math.PI)); // px
+ var padCursor = 'crosshair';
+
+ // wrap
+ p.wrap.className = 'jscolor-picker-wrap';
+ p.wrap.style.clear = 'both';
+ p.wrap.style.width = pickerDims.borderW + 'px';
+ p.wrap.style.height = pickerDims.borderH + 'px';
+ p.wrap.style.zIndex = THIS.zIndex;
+
+ // picker
+ p.box.className = 'jscolor-picker';
+ p.box.style.width = pickerDims.paddedW + 'px';
+ p.box.style.height = pickerDims.paddedH + 'px';
+ p.box.style.position = 'relative';
+
+ // picker shadow
+ p.boxS.className = 'jscolor-picker-shadow';
+ p.boxS.style.position = 'absolute';
+ p.boxS.style.left = '0';
+ p.boxS.style.top = '0';
+ p.boxS.style.width = '100%';
+ p.boxS.style.height = '100%';
+ jsc.setBorderRadius(p.boxS, borderRadius + 'px');
+
+ // picker border
+ p.boxB.className = 'jscolor-picker-border';
+ p.boxB.style.position = 'relative';
+ p.boxB.style.border = THIS.borderWidth + 'px solid';
+ p.boxB.style.borderColor = THIS.borderColor;
+ p.boxB.style.background = THIS.backgroundColor;
+ jsc.setBorderRadius(p.boxB, borderRadius + 'px');
+
+ // IE hack:
+ // If the element is transparent, IE will trigger the event on the elements under it,
+ // e.g. on Canvas or on elements with border
+ p.padM.style.background = 'rgba(255,0,0,.2)';
+ p.sldM.style.background = 'rgba(0,255,0,.2)';
+ p.asldM.style.background = 'rgba(0,0,255,.2)';
+
+ p.padM.style.opacity =
+ p.sldM.style.opacity =
+ p.asldM.style.opacity =
+ '0';
+
+ // pad
+ p.pad.style.position = 'relative';
+ p.pad.style.width = THIS.width + 'px';
+ p.pad.style.height = THIS.height + 'px';
+
+ // pad - color spectrum (HSV and HVS)
+ p.padCanvas.draw(THIS.width, THIS.height, jsc.getPadYChannel(THIS));
+
+ // pad border
+ p.padB.style.position = 'absolute';
+ p.padB.style.left = THIS.padding + 'px';
+ p.padB.style.top = THIS.padding + 'px';
+ p.padB.style.border = THIS.controlBorderWidth + 'px solid';
+ p.padB.style.borderColor = THIS.controlBorderColor;
+
+ // pad mouse area
+ p.padM.style.position = 'absolute';
+ p.padM.style.left = 0 + 'px';
+ p.padM.style.top = 0 + 'px';
+ p.padM.style.width = (THIS.padding + 2 * THIS.controlBorderWidth + THIS.width + controlPadding) + 'px';
+ p.padM.style.height = (2 * THIS.controlBorderWidth + 2 * THIS.padding + THIS.height) + 'px';
+ p.padM.style.cursor = padCursor;
+ jsc.setData(p.padM, {
+ instance: THIS,
+ control: 'pad',
+ })
+
+ // pad cross
+ p.cross.style.position = 'absolute';
+ p.cross.style.left =
+ p.cross.style.top =
+ '0';
+ p.cross.style.width =
+ p.cross.style.height =
+ crossOuterSize + 'px';
+
+ // pad cross border Y and X
+ p.crossBY.style.position =
+ p.crossBX.style.position =
+ 'absolute';
+ p.crossBY.style.background =
+ p.crossBX.style.background =
+ THIS.pointerBorderColor;
+ p.crossBY.style.width =
+ p.crossBX.style.height =
+ (2 * THIS.pointerBorderWidth + THIS.pointerThickness) + 'px';
+ p.crossBY.style.height =
+ p.crossBX.style.width =
+ crossOuterSize + 'px';
+ p.crossBY.style.left =
+ p.crossBX.style.top =
+ (Math.floor(crossOuterSize / 2) - Math.floor(THIS.pointerThickness / 2) - THIS.pointerBorderWidth) + 'px';
+ p.crossBY.style.top =
+ p.crossBX.style.left =
+ '0';
+
+ // pad cross line Y and X
+ p.crossLY.style.position =
+ p.crossLX.style.position =
+ 'absolute';
+ p.crossLY.style.background =
+ p.crossLX.style.background =
+ THIS.pointerColor;
+ p.crossLY.style.height =
+ p.crossLX.style.width =
+ (crossOuterSize - 2 * THIS.pointerBorderWidth) + 'px';
+ p.crossLY.style.width =
+ p.crossLX.style.height =
+ THIS.pointerThickness + 'px';
+ p.crossLY.style.left =
+ p.crossLX.style.top =
+ (Math.floor(crossOuterSize / 2) - Math.floor(THIS.pointerThickness / 2)) + 'px';
+ p.crossLY.style.top =
+ p.crossLX.style.left =
+ THIS.pointerBorderWidth + 'px';
+
+
+ // slider
+ p.sld.style.overflow = 'hidden';
+ p.sld.style.width = THIS.sliderSize + 'px';
+ p.sld.style.height = THIS.height + 'px';
+
+ // slider gradient
+ p.sldGrad.draw(THIS.sliderSize, THIS.height, '#000', '#000');
+
+ // slider border
+ p.sldB.style.display = displaySlider ? 'block' : 'none';
+ p.sldB.style.position = 'absolute';
+ p.sldB.style.left = (THIS.padding + THIS.width + 2 * THIS.controlBorderWidth + 2 * controlPadding) + 'px';
+ p.sldB.style.top = THIS.padding + 'px';
+ p.sldB.style.border = THIS.controlBorderWidth + 'px solid';
+ p.sldB.style.borderColor = THIS.controlBorderColor;
+
+ // slider mouse area
+ p.sldM.style.display = displaySlider ? 'block' : 'none';
+ p.sldM.style.position = 'absolute';
+ p.sldM.style.left = (THIS.padding + THIS.width + 2 * THIS.controlBorderWidth + controlPadding) + 'px';
+ p.sldM.style.top = 0 + 'px';
+ p.sldM.style.width = (
+ (THIS.sliderSize + 2 * controlPadding + 2 * THIS.controlBorderWidth) +
+ (displayAlphaSlider ? 0 : Math.max(0, THIS.padding - controlPadding)) // remaining padding to the right edge
+ ) + 'px';
+ p.sldM.style.height = (2 * THIS.controlBorderWidth + 2 * THIS.padding + THIS.height) + 'px';
+ p.sldM.style.cursor = 'default';
+ jsc.setData(p.sldM, {
+ instance: THIS,
+ control: 'sld',
+ });
+
+ // slider pointer inner and outer border
+ p.sldPtrIB.style.border =
+ p.sldPtrOB.style.border =
+ THIS.pointerBorderWidth + 'px solid ' + THIS.pointerBorderColor;
+
+ // slider pointer outer border
+ p.sldPtrOB.style.position = 'absolute';
+ p.sldPtrOB.style.left = -(2 * THIS.pointerBorderWidth + THIS.pointerThickness) + 'px';
+ p.sldPtrOB.style.top = '0';
+
+ // slider pointer middle border
+ p.sldPtrMB.style.border = THIS.pointerThickness + 'px solid ' + THIS.pointerColor;
+
+ // slider pointer spacer
+ p.sldPtrS.style.width = THIS.sliderSize + 'px';
+ p.sldPtrS.style.height = jsc.pub.sliderInnerSpace + 'px';
+
+
+ // alpha slider
+ p.asld.style.overflow = 'hidden';
+ p.asld.style.width = THIS.sliderSize + 'px';
+ p.asld.style.height = THIS.height + 'px';
+
+ // alpha slider gradient
+ p.asldGrad.draw(THIS.sliderSize, THIS.height, '#000');
+
+ // alpha slider border
+ p.asldB.style.display = displayAlphaSlider ? 'block' : 'none';
+ p.asldB.style.position = 'absolute';
+ p.asldB.style.left = (
+ (THIS.padding + THIS.width + 2 * THIS.controlBorderWidth + controlPadding) +
+ (displaySlider ? (THIS.sliderSize + 3 * controlPadding + 2 * THIS.controlBorderWidth) : 0)
+ ) + 'px';
+ p.asldB.style.top = THIS.padding + 'px';
+ p.asldB.style.border = THIS.controlBorderWidth + 'px solid';
+ p.asldB.style.borderColor = THIS.controlBorderColor;
+
+ // alpha slider mouse area
+ p.asldM.style.display = displayAlphaSlider ? 'block' : 'none';
+ p.asldM.style.position = 'absolute';
+ p.asldM.style.left = (
+ (THIS.padding + THIS.width + 2 * THIS.controlBorderWidth + controlPadding) +
+ (displaySlider ? (THIS.sliderSize + 2 * controlPadding + 2 * THIS.controlBorderWidth) : 0)
+ ) + 'px';
+ p.asldM.style.top = 0 + 'px';
+ p.asldM.style.width = (
+ (THIS.sliderSize + 2 * controlPadding + 2 * THIS.controlBorderWidth) +
+ Math.max(0, THIS.padding - controlPadding) // remaining padding to the right edge
+ ) + 'px';
+ p.asldM.style.height = (2 * THIS.controlBorderWidth + 2 * THIS.padding + THIS.height) + 'px';
+ p.asldM.style.cursor = 'default';
+ jsc.setData(p.asldM, {
+ instance: THIS,
+ control: 'asld',
+ })
+
+ // alpha slider pointer inner and outer border
+ p.asldPtrIB.style.border =
+ p.asldPtrOB.style.border =
+ THIS.pointerBorderWidth + 'px solid ' + THIS.pointerBorderColor;
+
+ // alpha slider pointer outer border
+ p.asldPtrOB.style.position = 'absolute';
+ p.asldPtrOB.style.left = -(2 * THIS.pointerBorderWidth + THIS.pointerThickness) + 'px';
+ p.asldPtrOB.style.top = '0';
+
+ // alpha slider pointer middle border
+ p.asldPtrMB.style.border = THIS.pointerThickness + 'px solid ' + THIS.pointerColor;
+
+ // alpha slider pointer spacer
+ p.asldPtrS.style.width = THIS.sliderSize + 'px';
+ p.asldPtrS.style.height = jsc.pub.sliderInnerSpace + 'px';
+
+
+ // palette
+ p.pal.className = 'jscolor-palette';
+ p.pal.style.display = pickerDims.palette.rows ? 'block' : 'none';
+ p.pal.style.position = 'absolute';
+ p.pal.style.left = THIS.padding + 'px';
+ p.pal.style.top = (2 * THIS.controlBorderWidth + 2 * THIS.padding + THIS.height) + 'px';
+
+ // palette's color samples
+
+ p.pal.innerHTML = '';
+
+ var chessboard = jsc.genColorPreviewCanvas('rgba(0,0,0,0)');
+
+ var si = 0; // color sample's index
+ for (var r = 0; r < pickerDims.palette.rows; r++) {
+ for (var c = 0; c < pickerDims.palette.cols && si < THIS._palette.length; c++, si++) {
+ var sampleColor = THIS._palette[si];
+ var sampleCssColor = jsc.rgbaColor.apply(null, sampleColor.rgba);
+
+ var sc = jsc.createEl('div'); // color sample's color
+ sc.style.width = (pickerDims.palette.cellW - 2 * THIS.controlBorderWidth) + 'px';
+ sc.style.height = (pickerDims.palette.cellH - 2 * THIS.controlBorderWidth) + 'px';
+ sc.style.backgroundColor = sampleCssColor;
+
+ var sw = jsc.createEl('div'); // color sample's wrap
+ sw.className = 'jscolor-palette-sample';
+ sw.style.display = 'block';
+ sw.style.position = 'absolute';
+ sw.style.left = (
+ pickerDims.palette.cols <= 1 ? 0 :
+ Math.round(10 * (c * ((pickerDims.contentW - pickerDims.palette.cellW) / (pickerDims.palette.cols - 1)))) / 10
+ ) + 'px';
+ sw.style.top = (r * (pickerDims.palette.cellH + THIS.paletteSpacing)) + 'px';
+ sw.style.border = THIS.controlBorderWidth + 'px solid';
+ sw.style.borderColor = THIS.controlBorderColor;
+ sw.style.cursor = 'pointer';
+ if (sampleColor.rgba[3] !== null && sampleColor.rgba[3] < 1.0) { // only create chessboard background if the sample has transparency
+ sw.style.backgroundImage = 'url(\'' + chessboard.canvas.toDataURL() + '\')';
+ sw.style.backgroundRepeat = 'repeat';
+ sw.style.backgroundPosition = 'center center';
+ }
+ jsc.setData(sw, {
+ instance: THIS,
+ control: 'palette-sample',
+ color: sampleColor,
+ })
+ sw.addEventListener('click', jsc.onPaletteSampleClick, false);
+ sw.appendChild(sc);
+ p.pal.appendChild(sw);
+ }
+ }
+
+
+ // the Close button
+ function setBtnBorder () {
+ var insetColors = THIS.controlBorderColor.split(/\s+/);
+ var outsetColor = insetColors.length < 2 ? insetColors[0] : insetColors[1] + ' ' + insetColors[0] + ' ' + insetColors[0] + ' ' + insetColors[1];
+ p.btn.style.borderColor = outsetColor;
+ }
+ var btnPadding = 15; // px
+ p.btn.className = 'jscolor-btn-close';
+ p.btn.style.display = THIS.closeButton ? 'block' : 'none';
+ p.btn.style.position = 'absolute';
+ p.btn.style.left = THIS.padding + 'px';
+ p.btn.style.bottom = THIS.padding + 'px';
+ p.btn.style.padding = '0 ' + btnPadding + 'px';
+ p.btn.style.maxWidth = (pickerDims.contentW - 2 * THIS.controlBorderWidth - 2 * btnPadding) + 'px';
+ p.btn.style.overflow = 'hidden';
+ p.btn.style.height = THIS.buttonHeight + 'px';
+ p.btn.style.whiteSpace = 'nowrap';
+ p.btn.style.border = THIS.controlBorderWidth + 'px solid';
+ setBtnBorder();
+ p.btn.style.color = THIS.buttonColor;
+ p.btn.style.font = '12px sans-serif';
+ p.btn.style.textAlign = 'center';
+ p.btn.style.cursor = 'pointer';
+ p.btn.onmousedown = function () {
+ THIS.hide();
+ };
+ p.btnT.style.lineHeight = THIS.buttonHeight + 'px';
+ p.btnT.innerHTML = '';
+ p.btnT.appendChild(window.document.createTextNode(THIS.closeText));
+
+ // reposition the pointers
+ redrawPad();
+ redrawSld();
+ redrawASld();
+
+ // If we are changing the owner without first closing the picker,
+ // make sure to first deal with the old owner
+ if (jsc.picker.owner && jsc.picker.owner !== THIS) {
+ jsc.removeClass(jsc.picker.owner.targetElement, jsc.pub.activeClassName);
+ }
+
+ // Set a new picker owner
+ jsc.picker.owner = THIS;
+
+ // The redrawPosition() method needs picker.owner to be set, that's why we call it here,
+ // after setting the owner
+ if (THIS.container === window.document.body) {
+ jsc.redrawPosition();
+ } else {
+ jsc._drawPosition(THIS, 0, 0, 'relative', false);
+ }
+
+ if (p.wrap.parentNode !== THIS.container) {
+ THIS.container.appendChild(p.wrap);
+ }
+
+ jsc.addClass(THIS.targetElement, jsc.pub.activeClassName);
+ }
+
+
+ function redrawPad () {
+ // redraw the pad pointer
+ var yChannel = jsc.getPadYChannel(THIS);
+ var x = Math.round((THIS.channels.h / 360) * (THIS.width - 1));
+ var y = Math.round((1 - THIS.channels[yChannel] / 100) * (THIS.height - 1));
+ var crossOuterSize = (2 * THIS.pointerBorderWidth + THIS.pointerThickness + 2 * THIS.crossSize);
+ var ofs = -Math.floor(crossOuterSize / 2);
+ jsc.picker.cross.style.left = (x + ofs) + 'px';
+ jsc.picker.cross.style.top = (y + ofs) + 'px';
+
+ // redraw the slider
+ switch (jsc.getSliderChannel(THIS)) {
+ case 's':
+ var rgb1 = jsc.HSV_RGB(THIS.channels.h, 100, THIS.channels.v);
+ var rgb2 = jsc.HSV_RGB(THIS.channels.h, 0, THIS.channels.v);
+ var color1 = 'rgb(' +
+ Math.round(rgb1[0]) + ',' +
+ Math.round(rgb1[1]) + ',' +
+ Math.round(rgb1[2]) + ')';
+ var color2 = 'rgb(' +
+ Math.round(rgb2[0]) + ',' +
+ Math.round(rgb2[1]) + ',' +
+ Math.round(rgb2[2]) + ')';
+ jsc.picker.sldGrad.draw(THIS.sliderSize, THIS.height, color1, color2);
+ break;
+ case 'v':
+ var rgb = jsc.HSV_RGB(THIS.channels.h, THIS.channels.s, 100);
+ var color1 = 'rgb(' +
+ Math.round(rgb[0]) + ',' +
+ Math.round(rgb[1]) + ',' +
+ Math.round(rgb[2]) + ')';
+ var color2 = '#000';
+ jsc.picker.sldGrad.draw(THIS.sliderSize, THIS.height, color1, color2);
+ break;
+ }
+
+ // redraw the alpha slider
+ jsc.picker.asldGrad.draw(THIS.sliderSize, THIS.height, THIS.toHEXString());
+ }
+
+
+ function redrawSld () {
+ var sldChannel = jsc.getSliderChannel(THIS);
+ if (sldChannel) {
+ // redraw the slider pointer
+ var y = Math.round((1 - THIS.channels[sldChannel] / 100) * (THIS.height - 1));
+ jsc.picker.sldPtrOB.style.top = (y - (2 * THIS.pointerBorderWidth + THIS.pointerThickness) - Math.floor(jsc.pub.sliderInnerSpace / 2)) + 'px';
+ }
+
+ // redraw the alpha slider
+ jsc.picker.asldGrad.draw(THIS.sliderSize, THIS.height, THIS.toHEXString());
+ }
+
+
+ function redrawASld () {
+ var y = Math.round((1 - THIS.channels.a) * (THIS.height - 1));
+ jsc.picker.asldPtrOB.style.top = (y - (2 * THIS.pointerBorderWidth + THIS.pointerThickness) - Math.floor(jsc.pub.sliderInnerSpace / 2)) + 'px';
+ }
+
+
+ function isPickerOwner () {
+ return jsc.picker && jsc.picker.owner === THIS;
+ }
+
+
+ function onValueKeyDown (ev) {
+ if (jsc.eventKey(ev) === 'Enter') {
+ if (THIS.valueElement) {
+ THIS.processValueInput(THIS.valueElement.value);
+ }
+ THIS.tryHide();
+ }
+ }
+
+
+ function onAlphaKeyDown (ev) {
+ if (jsc.eventKey(ev) === 'Enter') {
+ if (THIS.alphaElement) {
+ THIS.processAlphaInput(THIS.alphaElement.value);
+ }
+ THIS.tryHide();
+ }
+ }
+
+
+ function onValueChange (ev) {
+ if (jsc.getData(ev, 'internal')) {
+ return; // skip if the event was internally triggered by jscolor
+ }
+
+ var oldVal = THIS.valueElement.value;
+
+ THIS.processValueInput(THIS.valueElement.value); // this might change the value
+
+ jsc.triggerCallback(THIS, 'onChange');
+
+ if (THIS.valueElement.value !== oldVal) {
+ // value was additionally changed -> let's trigger the change event again, even though it was natively dispatched
+ jsc.triggerInputEvent(THIS.valueElement, 'change', true, true);
+ }
+ }
+
+
+ function onAlphaChange (ev) {
+ if (jsc.getData(ev, 'internal')) {
+ return; // skip if the event was internally triggered by jscolor
+ }
+
+ var oldVal = THIS.alphaElement.value;
+
+ THIS.processAlphaInput(THIS.alphaElement.value); // this might change the value
+
+ jsc.triggerCallback(THIS, 'onChange');
+
+ // triggering valueElement's onChange (because changing alpha changes the entire color, e.g. with rgba format)
+ jsc.triggerInputEvent(THIS.valueElement, 'change', true, true);
+
+ if (THIS.alphaElement.value !== oldVal) {
+ // value was additionally changed -> let's trigger the change event again, even though it was natively dispatched
+ jsc.triggerInputEvent(THIS.alphaElement, 'change', true, true);
+ }
+ }
+
+
+ function onValueInput (ev) {
+ if (jsc.getData(ev, 'internal')) {
+ return; // skip if the event was internally triggered by jscolor
+ }
+
+ if (THIS.valueElement) {
+ THIS.fromString(THIS.valueElement.value, jsc.flags.leaveValue);
+ }
+
+ jsc.triggerCallback(THIS, 'onInput');
+
+ // triggering valueElement's onInput
+ // (not needed, it was dispatched normally by the browser)
+ }
+
+
+ function onAlphaInput (ev) {
+ if (jsc.getData(ev, 'internal')) {
+ return; // skip if the event was internally triggered by jscolor
+ }
+
+ if (THIS.alphaElement) {
+ THIS.fromHSVA(null, null, null, parseFloat(THIS.alphaElement.value), jsc.flags.leaveAlpha);
+ }
+
+ jsc.triggerCallback(THIS, 'onInput');
+
+ // triggering valueElement's onInput (because changing alpha changes the entire color, e.g. with rgba format)
+ jsc.triggerInputEvent(THIS.valueElement, 'input', true, true);
+ }
+
+
+ // let's process the DEPRECATED 'options' property (this will be later removed)
+ if (jsc.pub.options) {
+ // let's set custom default options, if specified
+ for (var opt in jsc.pub.options) {
+ if (jsc.pub.options.hasOwnProperty(opt)) {
+ try {
+ setOption(opt, jsc.pub.options[opt]);
+ } catch (e) {
+ console.warn(e);
+ }
+ }
+ }
+ }
+
+
+ // let's apply configuration presets
+ //
+ var presetsArr = [];
+
+ if (opts.preset) {
+ if (typeof opts.preset === 'string') {
+ presetsArr = opts.preset.split(/\s+/);
+ } else if (Array.isArray(opts.preset)) {
+ presetsArr = opts.preset.slice(); // slice() to clone
+ } else {
+ console.warn('Unrecognized preset value');
+ }
+ }
+
+ // always use the 'default' preset. If it's not listed, append it to the end.
+ if (presetsArr.indexOf('default') === -1) {
+ presetsArr.push('default');
+ }
+
+ // let's apply the presets in reverse order, so that should there be any overlapping options,
+ // the formerly listed preset will override the latter
+ for (var i = presetsArr.length - 1; i >= 0; i -= 1) {
+ var pres = presetsArr[i];
+ if (!pres) {
+ continue; // preset is empty string
+ }
+ if (!jsc.pub.presets.hasOwnProperty(pres)) {
+ console.warn('Unknown preset: %s', pres);
+ continue;
+ }
+ for (var opt in jsc.pub.presets[pres]) {
+ if (jsc.pub.presets[pres].hasOwnProperty(opt)) {
+ try {
+ setOption(opt, jsc.pub.presets[pres][opt]);
+ } catch (e) {
+ console.warn(e);
+ }
+ }
+ }
+ }
+
+
+ // let's set specific options for this color picker
+ var nonProperties = [
+ // these options won't be set as instance properties
+ 'preset',
+ ];
+ for (var opt in opts) {
+ if (opts.hasOwnProperty(opt)) {
+ if (nonProperties.indexOf(opt) === -1) {
+ try {
+ setOption(opt, opts[opt]);
+ } catch (e) {
+ console.warn(e);
+ }
+ }
+ }
+ }
+
+
+ //
+ // Install the color picker on chosen element(s)
+ //
+
+
+ // Determine picker's container element
+ if (this.container === undefined) {
+ this.container = window.document.body; // default container is BODY element
+
+ } else { // explicitly set to custom element
+ this.container = jsc.node(this.container);
+ }
+
+ if (!this.container) {
+ throw new Error('Cannot instantiate color picker without a container element');
+ }
+
+
+ // Fetch the target element
+ this.targetElement = jsc.node(targetElement);
+
+ if (!this.targetElement) {
+ // temporarily customized error message to help with migrating from versions prior to 2.2
+ if (typeof targetElement === 'string' && /^[a-zA-Z][\w:.-]*$/.test(targetElement)) {
+ // targetElement looks like valid ID
+ var possiblyId = targetElement;
+ throw new Error('If \'' + possiblyId + '\' is supposed to be an ID, please use \'#' + possiblyId + '\' or any valid CSS selector.');
+ }
+
+ throw new Error('Cannot instantiate color picker without a target element');
+ }
+
+ if (this.targetElement.jscolor && this.targetElement.jscolor instanceof jsc.pub) {
+ throw new Error('Color picker already installed on this element');
+ }
+
+
+ // link this instance with the target element
+ this.targetElement.jscolor = this;
+ jsc.addClass(this.targetElement, jsc.pub.className);
+
+ // register this instance
+ jsc.instances.push(this);
+
+
+ // if target is BUTTON
+ if (jsc.isButton(this.targetElement)) {
+
+ if (this.targetElement.type.toLowerCase() !== 'button') {
+ // on buttons, always force type to be 'button', e.g. in situations the target