From 16ad8f78dc8cfd05ad8191e213d2aa9cd1536045 Mon Sep 17 00:00:00 2001 From: Daniel van Noord Date: Fri, 29 Jan 2021 18:22:57 +0100 Subject: [PATCH] Update JSColor to 2.4.5 --- jscolor/jscolor.js | 4213 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 3370 insertions(+), 843 deletions(-) diff --git a/jscolor/jscolor.js b/jscolor/jscolor.js index ef3bce5..df9d094 100644 --- a/jscolor/jscolor.js +++ b/jscolor/jscolor.js @@ -1,997 +1,3524 @@ /** - * jscolor, JavaScript Color Picker + * 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 + * @license For open source use: GPLv3 + * For commercial use: JSColor Commercial License + * @author Jan Odvarko - East Desire + * @version 2.4.5 + * + * See usage examples at http://jscolor.com/examples/ */ -var jscolor = { +(function (global, factory) { + + 'use strict'; + + if (typeof module === 'object' && typeof module.exports === 'object') { + // Export jscolor as a module + module.exports = global.document ? + factory (global) : + function (win) { + if (!win.document) { + throw new Error('jscolor needs a window with document'); + } + return factory(win); + } + return; + } + + // Default use (no module export) + factory(global); + +})(typeof window !== 'undefined' ? window : this, function (window) { // BEGIN factory + +// BEGIN jscolor code - dir : '', // location of jscolor directory (leave empty to autodetect) - bindClass : 'color', // class name - binding : true, // automatic binding via - preloading : true, // use image preloading? +'use strict'; - install : function() { - jscolor.addEvent(window, 'load', jscolor.init); - }, +var jscolor = (function () { // BEGIN jscolor + +var jsc = { - init : function() { - if(jscolor.binding) { - jscolor.bind(); - } - if(jscolor.preloading) { - jscolor.preload(); + initialized : false, + + instances : [], // created instances of jscolor + + readyQueue : [], // functions waiting to be called after init + + + register : function () { + if (typeof window !== 'undefined' && window.document) { + window.document.addEventListener('DOMContentLoaded', jsc.pub.init, false); } }, - getDir : function() { - if(!jscolor.dir) { - var detected = jscolor.detectDir(); - jscolor.dir = detected!==false ? detected : 'jscolor/'; + installBySelector : function (selector, rootNode) { + rootNode = rootNode ? jsc.node(rootNode) : window.document; + if (!rootNode) { + throw new Error('Missing root node'); + } + + var elms = rootNode.querySelectorAll(selector); + + // for backward compatibility with DEPRECATED installation/configuration using className + var matchClass = new RegExp('(^|\\s)(' + jsc.pub.lookupClass + ')(\\s*(\\{[^}]*\\})|\\s|$)', 'i'); + + for (var i = 0; i < elms.length; i += 1) { + + if (elms[i].jscolor && elms[i].jscolor instanceof jsc.pub) { + continue; // jscolor already installed on this element + } + + if (elms[i].type !== undefined && elms[i].type.toLowerCase() == 'color' && jsc.isColorAttrSupported) { + continue; // skips inputs of type 'color' if supported by the browser + } + + var dataOpts, m; + + if ( + (dataOpts = jsc.getDataAttr(elms[i], 'jscolor')) !== null || + (elms[i].className && (m = elms[i].className.match(matchClass))) // installation using className (DEPRECATED) + ) { + var targetElm = elms[i]; + + var optsStr = ''; + if (dataOpts !== null) { + optsStr = dataOpts; + + } else if (m) { // installation using className (DEPRECATED) + console.warn('Installation using class name is DEPRECATED. Use data-jscolor="" attribute instead.' + jsc.docsRef); + if (m[4]) { + optsStr = m[4]; + } + } + + var opts = null; + if (optsStr.trim()) { + try { + opts = jsc.parseOptionsStr(optsStr); + } catch (e) { + console.warn(e + '\n' + optsStr); + } + } + + try { + new jsc.pub(targetElm, opts); + } catch (e) { + console.warn(e); + } + } } - return jscolor.dir; }, - detectDir : function() { - var base = location.href; + parseOptionsStr : function (str) { + var opts = null; - var e = document.getElementsByTagName('base'); - for(var i=0; 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; } - var e = document.getElementsByTagName('script'); - for(var i=0; i -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; }, - bind : function() { - var matchClass = new RegExp('(^|\\s)('+jscolor.bindClass+')(\\s*(\\{[^}]*\\})|\\s|$)', 'i'); - var e = document.getElementsByTagName('input'); - for(var i=0; i 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); } - e[i].color = new jscolor.color(e[i], prop); } - } - }, + var grad = ctx.createLinearGradient(0, 0, 0, canvas.height); + grad.addColorStop(0, color); + grad.addColorStop(1, 'rgba(0,0,0,0)'); - preload : function() { - for(var fn in jscolor.imgRequire) { - if(jscolor.imgRequire.hasOwnProperty(fn)) { - jscolor.loadImage(fn); - } - } - }, - - - images : { - pad : [ 181, 101 ], - sld : [ 16, 101 ], - cross : [ 15, 15 ], - arrow : [ 7, 11 ] - }, - - - imgRequire : {}, - imgLoaded : {}, - - - requireImage : function(filename) { - jscolor.imgRequire[filename] = true; - }, - - - loadImage : function(filename) { - if(!jscolor.imgLoaded[filename]) { - jscolor.imgLoaded[filename] = new Image(); - jscolor.imgLoaded[filename].src = jscolor.getDir()+filename; - } - }, - - - fetchElement : function(mixed) { - return typeof mixed === 'string' ? document.getElementById(mixed) : mixed; - }, - - - addEvent : function(el, evnt, func) { - if(el.addEventListener) { - el.addEventListener(evnt, func, false); - } else if(el.attachEvent) { - el.attachEvent('on'+evnt, func); - } - }, - - - fireEvent : function(el, evnt) { - if(!el) { - return; - } - if(document.createEvent) { - var ev = document.createEvent('HTMLEvents'); - ev.initEvent(evnt, true, true); - el.dispatchEvent(ev); - } else if(document.createEventObject) { - var ev = document.createEventObject(); - el.fireEvent('on'+evnt, ev); - } else if(el['on'+evnt]) { // alternatively use the traditional event model (IE5) - el['on'+evnt](); - } - }, - - - getElementPos : function(e) { - var e1=e, e2=e; - var x=0, y=0; - if(e1.offsetParent) { - do { - x += e1.offsetLeft; - y += e1.offsetTop; - } while(e1 = e1.offsetParent); - } - while((e2 = e2.parentNode) && e2.nodeName.toUpperCase() !== 'BODY') { - x -= e2.scrollLeft; - y -= e2.scrollTop; - } - return [x, y]; - }, - - - getElementSize : function(e) { - return [e.offsetWidth, e.offsetHeight]; - }, - - - getRelMousePos : function(e) { - var x = 0, y = 0; - if (!e) { e = window.event; } - if (typeof e.offsetX === 'number') { - x = e.offsetX; - y = e.offsetY; - } else if (typeof e.layerX === 'number') { - x = e.layerX; - y = e.layerY; - } - return { x: x, y: y }; - }, - - - getViewPos : function() { - if(typeof window.pageYOffset === 'number') { - return [window.pageXOffset, window.pageYOffset]; - } else if(document.body && (document.body.scrollLeft || document.body.scrollTop)) { - return [document.body.scrollLeft, document.body.scrollTop]; - } else if(document.documentElement && (document.documentElement.scrollLeft || document.documentElement.scrollTop)) { - return [document.documentElement.scrollLeft, document.documentElement.scrollTop]; - } else { - return [0, 0]; - } - }, - - - getViewSize : function() { - if(typeof window.innerWidth === 'number') { - return [window.innerWidth, window.innerHeight]; - } else if(document.body && (document.body.clientWidth || document.body.clientHeight)) { - return [document.body.clientWidth, document.body.clientHeight]; - } else if(document.documentElement && (document.documentElement.clientWidth || document.documentElement.clientHeight)) { - return [document.documentElement.clientWidth, document.documentElement.clientHeight]; - } else { - return [0, 0]; - } - }, - - - URI : function(uri) { // See RFC3986 - - this.scheme = null; - this.authority = null; - this.path = ''; - this.query = null; - this.fragment = null; - - this.parse = function(uri) { - var m = uri.match(/^(([A-Za-z][0-9A-Za-z+.-]*)(:))?((\/\/)([^\/?#]*))?([^?#]*)((\?)([^#]*))?((#)(.*))?/); - this.scheme = m[3] ? m[2] : null; - this.authority = m[5] ? m[6] : null; - this.path = m[7]; - this.query = m[9] ? m[10] : null; - this.fragment = m[12] ? m[13] : null; - return this; + ctx.fillStyle = grad; + ctx.fillRect(0, 0, canvas.width, canvas.height); }; - this.toString = function() { - var result = ''; - if(this.scheme !== null) { result = result + this.scheme + ':'; } - if(this.authority !== null) { result = result + '//' + this.authority; } - if(this.path !== null) { result = result + this.path; } - if(this.query !== null) { result = result + '?' + this.query; } - if(this.fragment !== null) { result = result + '#' + this.fragment; } - return result; + 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; }; - this.toAbsolute = function(base) { - var base = new jscolor.URI(base); - var r = this; - var t = new jscolor.URI; + 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(' '); + }; - if(base.scheme === null) { return false; } + return BoxShadow; + })(), - if(r.scheme !== null && r.scheme.toLowerCase() === base.scheme.toLowerCase()) { - r.scheme = null; + + 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(r.scheme !== null) { - t.scheme = r.scheme; - t.authority = r.authority; - t.path = removeDotSegments(r.path); - t.query = r.query; - } else { - if(r.authority !== null) { - t.authority = r.authority; - t.path = removeDotSegments(r.path); - t.query = r.query; - } else { - if(r.path === '') { - t.path = base.path; - if(r.query !== null) { - t.query = r.query; - } else { - t.query = base.query; - } - } else { - if(r.path.substr(0,1) === '/') { - t.path = removeDotSegments(r.path); - } else { - if(base.authority !== null && base.path === '') { - t.path = '/'+r.path; - } else { - t.path = base.path.replace(/[^\/]+$/,'')+r.path; + 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; } - t.path = removeDotSegments(t.path); + } catch (e) { + console.warn(e); + success = false; } - t.query = r.query; } - t.authority = base.authority; } - t.scheme = base.scheme; + 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; } - t.fragment = r.fragment; - return t; - }; - - function removeDotSegments(path) { - var out = ''; - while(path) { - if(path.substr(0,3)==='../' || path.substr(0,2)==='./') { - path = path.replace(/^\.+/,'').substr(1); - } else if(path.substr(0,3)==='/./' || path==='/.') { - path = '/'+path.substr(3); - } else if(path.substr(0,4)==='/../' || path==='/..') { - path = '/'+path.substr(4); - out = out.replace(/\/?[^\/]*$/, ''); - } else if(path==='.' || path==='..') { - path = ''; - } else { - var rm = path.match(/^\/?[^\/]*/)[0]; - path = path.substr(rm.length); - out = out + rm; - } - } - return out; - } - - if(uri) { - this.parse(uri); - } - - }, - - - // - // Usage example: - // var myColor = new jscolor.color(myInputElement) - // - - color : function(target, prop) { - - - this.required = true; // refuse empty values? - this.adjust = true; // adjust value to uniform notation? - this.hash = false; // prefix color with # symbol? - this.caps = true; // uppercase? - this.slider = true; // show the value/saturation slider? - this.valueElement = target; // value holder - this.styleElement = target; // where to reflect current color - this.onImmediateChange = null; // onchange callback (can be either string or function) - this.hsv = [0, 0, 1]; // read-only 0-6, 0-1, 0-1 - this.rgb = [1, 1, 1]; // read-only 0-1, 0-1, 0-1 - this.minH = 0; // read-only 0-6 - this.maxH = 6; // read-only 0-6 - this.minS = 0; // read-only 0-1 - this.maxS = 1; // read-only 0-1 - this.minV = 0; // read-only 0-1 - this.maxV = 1; // read-only 0-1 - - this.pickerOnfocus = true; // display picker on focus? - this.pickerMode = 'HSV'; // HSV | HVS - this.pickerPosition = 'bottom'; // left | right | top | bottom - this.pickerSmartPosition = true; // automatically adjust picker position when necessary - this.pickerButtonHeight = 20; // px - this.pickerClosable = false; - this.pickerCloseText = 'Close'; - this.pickerButtonColor = 'ButtonText'; // px - this.pickerFace = 10; // px - this.pickerFaceColor = 'ThreeDFace'; // CSS color - this.pickerBorder = 1; // px - this.pickerBorderColor = 'ThreeDHighlight ThreeDShadow ThreeDShadow ThreeDHighlight'; // CSS color - this.pickerInset = 1; // px - this.pickerInsetColor = 'ThreeDShadow ThreeDHighlight ThreeDHighlight ThreeDShadow'; // CSS color - this.pickerZIndex = 10000; - - - for(var p in prop) { - if(prop.hasOwnProperty(p)) { - this[p] = prop[p]; - } + throw new Error('Invalid arguments'); } - this.hidePicker = function() { - if(isPickerOwner()) { - removePicker(); + // 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); } - }; - - this.showPicker = function() { - if(!isPickerOwner()) { - var tp = jscolor.getElementPos(target); // target pos - var ts = jscolor.getElementSize(target); // target size - var vp = jscolor.getViewPos(); // view pos - var vs = jscolor.getViewSize(); // view size - var ps = getPickerDims(this); // picker size - var a, b, c; - switch(this.pickerPosition.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; + if (value === undefined) { + // getting channel value + if (!this.channels.hasOwnProperty(name.toLowerCase())) { + console.warn('Getting unknown channel: ' + name); + return false; } - var l = (ts[b]+ps[b])/2; + return this.channels[name.toLowerCase()]; - // picker pos - if (!this.pickerSmartPosition) { - 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) - ]; - } - 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(); + // 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; + } - 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(); + // 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); } }; - 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)); } + // 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; } - 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) + 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.exportColor(flags); + this.exposeColor(flags); + return true; }; - 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)); } + // 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; } - var hsv = RGB_HSV( - r===null ? this.rgb[0] : r, - g===null ? this.rgb[1] : g, - b===null ? this.rgb[2] : b + 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.hsv[0] = Math.max(0.0, this.minH, Math.min(6.0, this.maxH, hsv[0])); + if (hsv[0] !== null) { + this.channels.h = Math.max(0, Math.min(360, 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])); + 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.hsv[2] = hsv[2]===null ? null : Math.max(0.0, this.minV, Math.min(1.0, this.maxV, hsv[2])); + 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 = 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]; + 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.exportColor(flags); + this.exposeColor(flags); + return true; }; - 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 - ); - } + // 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() { - 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) + 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 ); }; - 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 ]; - } + this.toHEXAString = function () { + return jsc.hexaColor( + this.channels.r, + this.channels.g, + this.channels.b, + this.channels.a + ); + }; - 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]; + 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(); } - } + }; - function removePicker() { - delete jscolor.picker.owner; - document.getElementsByTagName('body')[0].removeChild(jscolor.picker.boxB); - } + this.show = function () { + drawPicker(); + }; - 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 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', }); } - p.sldM.onmouseup = - p.sldM.onmouseout = function() { if(holdSld) { holdSld=false; jscolor.fireEvent(valueElement,'change'); } }; - p.sldM.onmousedown = function(e) { - holdPad=false; - holdSld=true; - setSld(e); - dispatchImmediateChange(); + + var bg = { + image: [], + position: [], + size: [], + repeat: [], + origin: [], }; - if('ontouchstart' in window) { - p.sldM.addEventListener('touchstart', function(e) { - touchOffset={ - 'X': e.target.offsetParent.offsetLeft, - 'Y': e.target.offsetParent.offsetTop - }; - this.onmousedown({ - 'offsetX':e.touches[0].pageX-touchOffset.X, - 'offsetY':e.touches[0].pageY-touchOffset.Y - }); - }); + 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 - var dims = getPickerDims(THIS); - p.box.style.width = dims[0] + 'px'; - p.box.style.height = dims[1] + 'px'; + 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.style.position = 'absolute'; - p.boxB.style.clear = 'both'; - p.boxB.style.left = x+'px'; - p.boxB.style.top = y+'px'; - p.boxB.style.zIndex = THIS.pickerZIndex; - p.boxB.style.border = THIS.pickerBorder+'px solid'; - p.boxB.style.borderColor = THIS.pickerBorderColor; - p.boxB.style.background = THIS.pickerFaceColor; + 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'); - // pad image - p.pad.style.width = jscolor.images.pad[0]+'px'; - p.pad.style.height = jscolor.images.pad[1]+'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.pickerFace+'px'; - p.padB.style.top = THIS.pickerFace+'px'; - p.padB.style.border = THIS.pickerInset+'px solid'; - p.padB.style.borderColor = THIS.pickerInsetColor; + 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'; - p.padM.style.top = '0'; - p.padM.style.width = THIS.pickerFace + 2*THIS.pickerInset + jscolor.images.pad[0] + jscolor.images.arrow[0] + 'px'; - p.padM.style.height = p.box.style.height; - p.padM.style.cursor = 'crosshair'; + 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', + }) - // slider image + // 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 = jscolor.images.sld[0]+'px'; - p.sld.style.height = jscolor.images.sld[1]+'px'; + 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 = THIS.slider ? 'block' : 'none'; + p.sldB.style.display = displaySlider ? 'block' : 'none'; p.sldB.style.position = 'absolute'; - p.sldB.style.right = THIS.pickerFace+'px'; - p.sldB.style.top = THIS.pickerFace+'px'; - p.sldB.style.border = THIS.pickerInset+'px solid'; - p.sldB.style.borderColor = THIS.pickerInsetColor; + 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 = THIS.slider ? 'block' : 'none'; + p.sldM.style.display = displaySlider ? 'block' : 'none'; p.sldM.style.position = 'absolute'; - p.sldM.style.right = '0'; - p.sldM.style.top = '0'; - p.sldM.style.width = jscolor.images.sld[0] + jscolor.images.arrow[0] + THIS.pickerFace + 2*THIS.pickerInset + 'px'; - p.sldM.style.height = p.box.style.height; - try { - p.sldM.style.cursor = 'pointer'; - } catch(eOldIE) { - p.sldM.style.cursor = 'hand'; + 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); + } } - // "close" button - function setBtnBorder() { - var insetColors = THIS.pickerInsetColor.split(/\s+/); - var pickerOutsetColor = insetColors.length < 2 ? insetColors[0] : insetColors[1] + ' ' + insetColors[0] + ' ' + insetColors[0] + ' ' + insetColors[1]; - p.btn.style.borderColor = pickerOutsetColor; + + // 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; } - p.btn.style.display = THIS.pickerClosable ? 'block' : 'none'; + 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.pickerFace + 'px'; - p.btn.style.bottom = THIS.pickerFace + 'px'; - p.btn.style.padding = '0 15px'; - p.btn.style.height = '18px'; - p.btn.style.border = THIS.pickerInset + 'px solid'; + 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.pickerButtonColor; + p.btn.style.color = THIS.buttonColor; p.btn.style.font = '12px sans-serif'; p.btn.style.textAlign = 'center'; - try { - p.btn.style.cursor = 'pointer'; - } catch(eOldIE) { - p.btn.style.cursor = 'hand'; - } + p.btn.style.cursor = 'pointer'; p.btn.onmousedown = function () { - THIS.hidePicker(); + THIS.hide(); }; - p.btnS.style.lineHeight = p.btn.style.height; + p.btnT.style.lineHeight = THIS.buttonHeight + 'px'; + p.btnT.innerHTML = ''; + p.btnT.appendChild(window.document.createTextNode(THIS.closeText)); - // load images in optimal order - switch(modeID) { - case 0: var padImg = 'hs.png'; break; - case 1: var padImg = 'hv.png'; break; - } - p.padM.style.backgroundImage = "url('"+jscolor.getDir()+"cross.gif')"; - p.padM.style.backgroundRepeat = "no-repeat"; - p.sldM.style.backgroundImage = "url('"+jscolor.getDir()+"arrow.gif')"; - p.sldM.style.backgroundRepeat = "no-repeat"; - p.pad.style.backgroundImage = "url('"+jscolor.getDir()+padImg+"')"; - p.pad.style.backgroundRepeat = "no-repeat"; - p.pad.style.backgroundPosition = "0 0"; - - // place pointers + // reposition the pointers redrawPad(); redrawSld(); + redrawASld(); - jscolor.picker.owner = THIS; - document.getElementsByTagName('body')[0].appendChild(p.boxB); - } - - - function getPickerDims(o) { - var dims = [ - 2*o.pickerInset + 2*o.pickerFace + jscolor.images.pad[0] + - (o.slider ? 2*o.pickerInset + 2*jscolor.images.arrow[0] + jscolor.images.sld[0] : 0), - o.pickerClosable ? - 4*o.pickerInset + 3*o.pickerFace + jscolor.images.pad[1] + o.pickerButtonHeight : - 2*o.pickerInset + 2*o.pickerFace + jscolor.images.pad[1] - ]; - return dims; - } - - - function redrawPad() { - // redraw the pad pointer - switch(modeID) { - case 0: var yComponent = 1; break; - case 1: var yComponent = 2; break; + // 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); } - var x = Math.round((THIS.hsv[0]/6) * (jscolor.images.pad[0]-1)); - var y = Math.round((1-THIS.hsv[yComponent]) * (jscolor.images.pad[1]-1)); - jscolor.picker.padM.style.backgroundPosition = - (THIS.pickerFace+THIS.pickerInset+x - Math.floor(jscolor.images.cross[0]/2)) + 'px ' + - (THIS.pickerFace+THIS.pickerInset+y - Math.floor(jscolor.images.cross[1]/2)) + 'px'; - // redraw the slider image - var seg = jscolor.picker.sld.childNodes; + // Set a new picker owner + jsc.picker.owner = THIS; - switch(modeID) { - case 0: - var rgb = HSV_RGB(THIS.hsv[0], THIS.hsv[1], 1); - for(var i=0; i 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