PHP Classes

File: script/jscolor.js

Recommend this page to a friend!
  Classes of Stefan Kientzler   Complex PHP Form Design and Generator   script/jscolor.js   Download  
File: script/jscolor.js
Role: Auxiliary data
Content type: text/plain
Description: Auxiliary data
Class: Complex PHP Form Design and Generator
Display and process complex HTML forms HTML forms
Author: By
Last change:
Date: 2 years ago
Size: 98,582 bytes
 

Contents

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